In this document we present the joint analysis of the PASS1A metabolomics datasets.
Load all datasets
Load the data from the cloud, including: phenotypic data, metabolomic datasets, and metabolomics dictionary.
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/supervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/unsupervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/gcp_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/association_analysis_methods.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/data_aux_functions.R")
source("~/Desktop/repos/motrpac/tools/prediction_ml_tools.R")
library(randomForest) # for classification tests
# Load the dmaqc data
merged_dmaqc_data = load_from_bucket("merged_dmaqc_data2019-10-15.RData",
"gs://bic_data_analysis/pass1a/pheno_dmaqc/",F)
merged_dmaqc_data = merged_dmaqc_data[[1]]
rownames(merged_dmaqc_data) = as.character(merged_dmaqc_data$vial_label)
# define the tissue variable
merged_dmaqc_data$tissue = merged_dmaqc_data$sampletypedescription
# define the time to freeze variable
merged_dmaqc_data$time_to_freeze = merged_dmaqc_data$calculated.variables.time_death_to_collect_min +
merged_dmaqc_data$calculated.variables.time_collect_to_freeze_min
# col time vs. control
# df = data.frame(
# bid = merged_dmaqc_data$bid,
# edta_col_time = merged_dmaqc_data$calculated.variables.edta_coll_time,
# time_to_freeze = merged_dmaqc_data$time_to_freeze,
# is_control = merged_dmaqc_data$animal.key.is_control,
# tp = merged_dmaqc_data$animal.key.timepoint,
# tissue = merged_dmaqc_data$specimen.processing.sampletypedescription
# )
# df = unique(df)
# boxplot(edta_col_time/3600 ~ is_control,df)
# boxplot(edta_col_time/3600 - tp ~ is_control,df)
# wilcox.test(edta_col_time/3600 ~ is_control,df)
# blood freeze times
blood_samples =
merged_dmaqc_data$specimen.processing.sampletypedescription ==
"EDTA Plasma"
blood_freeze_time =
as.difftime(merged_dmaqc_data$specimen.processing.t_freeze,units = "mins") -
as.difftime(merged_dmaqc_data$specimen.processing.t_edtaspin,units="mins")
blood_freeze_time = as.numeric(blood_freeze_time)
time_to_freeze = merged_dmaqc_data$time_to_freeze[blood_samples] =
blood_freeze_time[blood_samples]
# Load our parsed metabolomics datasets
metabolomics_bucket_obj = load_from_bucket(
file = "metabolomics_parsed_datasets_pass1a_external1.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/")
metabolomics_parsed_datasets = metabolomics_bucket_obj$metabolomics_parsed_datasets
Define the variables to be adjusted for:
biospec_cols = c(
"acute.test.distance",
"calculated.variables.time_to_freeze",
# "calculated.variables.edta_coll_time", # no need - see code above for blood
"bid" # required for matching datasets
)
differential_analysis_cols = c(
"animal.registration.sex",
"animal.key.timepoint",
"animal.key.is_control"
)
pipeline_qc_cols = c("sample_order")
Log-transform: effect on variance
Some sites do not use the log transformation on their dataset. In this section we plot the coefficient of variation as a function of the mean instensity. We take a single dataset as an example to show how log-transformed data have reduced dependency and smoother plots.
As an additional analysis we also plot the number of missing values per metabolite as a function of its mean intensity. We show that while there is high correlation some missing values appear in fairely high intensities. This is important for imputation as some sites use some fixed low value instead of knn imputation.
# Plot cv vs means
library(gplots)
d = metabolomics_parsed_datasets[["white_adipose_powder,metab_u_hilicpos,unnamed"]]
dx = d$sample_data
CoV<-function(x){return(sd(x,na.rm = T)/mean(x,na.rm=T))}
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Raw data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")
# Repeat after log2
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Log2 data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")
# Plot number of NAs vs intensity mean
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
num_nas = rowSums(is.na(dx))
df = data.frame(Num_NAs = num_nas[inds],Mean_intensity = dmeans[inds])
rho = cor(df$Num_NAs,df$Mean_intensity,method="spearman")
rho = format(rho,digits=2)
plot(Num_NAs~Mean_intensity,df,cex=0.5,pch=20,
main=paste("Spearman:",rho))
Load the preprocessed and normalized data
metabolomics_processed_datasets = load_from_bucket(
file="metabolomics_processed_datasets11182019.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/"
)[[1]]
Copying gs://bic_data_analysis/pass1a/metabolomics/metabolomics_processed_datasets11182019.RData...
/ [0 files][ 0.0 B/480.7 MiB]
-
- [0 files][ 0.0 B/480.7 MiB]
\
\ [0 files][ 6.2 MiB/480.7 MiB]
|
/
/ [0 files][ 16.8 MiB/480.7 MiB]
-
\
\ [0 files][ 27.3 MiB/480.7 MiB]
|
| [0 files][ 38.2 MiB/480.7 MiB]
/
-
- [0 files][ 49.0 MiB/480.7 MiB]
\
|
| [0 files][ 58.8 MiB/480.7 MiB]
/
/ [0 files][ 69.4 MiB/480.7 MiB]
-
\
\ [0 files][ 80.2 MiB/480.7 MiB]
|
/
/ [0 files][ 91.0 MiB/480.7 MiB]
-
- [0 files][101.3 MiB/480.7 MiB] 10.2 MiB/s
\
|
| [0 files][110.6 MiB/480.7 MiB] 10.1 MiB/s
/
-
- [0 files][120.9 MiB/480.7 MiB] 10.0 MiB/s
\
\ [0 files][131.2 MiB/480.7 MiB] 10.0 MiB/s
|
/
/ [0 files][141.3 MiB/480.7 MiB] 9.9 MiB/s
-
\
\ [0 files][151.9 MiB/480.7 MiB] 9.9 MiB/s
|
| [0 files][162.2 MiB/480.7 MiB] 10.1 MiB/s
/
-
- [0 files][172.7 MiB/480.7 MiB] 10.3 MiB/s
\
\ [0 files][183.3 MiB/480.7 MiB] 10.3 MiB/s
|
/
/ [0 files][193.4 MiB/480.7 MiB] 10.3 MiB/s
-
\
\ [0 files][203.4 MiB/480.7 MiB] 10.2 MiB/s
|
| [0 files][213.0 MiB/480.7 MiB] 10.0 MiB/s
/
-
- [0 files][223.5 MiB/480.7 MiB] 10.0 MiB/s
\
|
| [0 files][233.8 MiB/480.7 MiB] 10.0 MiB/s
/
/ [0 files][243.4 MiB/480.7 MiB] 9.9 MiB/s
-
\
\ [0 files][252.9 MiB/480.7 MiB] 9.8 MiB/s
|
/
/ [0 files][263.0 MiB/480.7 MiB] 9.9 MiB/s
-
- [0 files][273.5 MiB/480.7 MiB] 9.8 MiB/s
\
|
| [0 files][283.6 MiB/480.7 MiB] 9.8 MiB/s
/
/ [0 files][293.1 MiB/480.7 MiB] 9.8 MiB/s
-
\
\ [0 files][299.2 MiB/480.7 MiB] 9.1 MiB/s
|
/
/ [0 files][307.7 MiB/480.7 MiB] 8.7 MiB/s
-
- [0 files][317.2 MiB/480.7 MiB] 8.5 MiB/s
\
|
| [0 files][327.3 MiB/480.7 MiB] 8.5 MiB/s
/
-
- [0 files][337.9 MiB/480.7 MiB] 8.7 MiB/s
\
\ [0 files][347.7 MiB/480.7 MiB] 9.5 MiB/s
|
/
/ [0 files][357.7 MiB/480.7 MiB] 9.8 MiB/s
-
\
\ [0 files][367.8 MiB/480.7 MiB] 10.0 MiB/s
|
| [0 files][377.6 MiB/480.7 MiB] 10.0 MiB/s
/
-
- [0 files][387.6 MiB/480.7 MiB] 9.9 MiB/s
\
|
| [0 files][397.7 MiB/480.7 MiB] 9.8 MiB/s
/
/ [0 files][408.0 MiB/480.7 MiB] 9.9 MiB/s
-
\
\ [0 files][418.1 MiB/480.7 MiB] 9.8 MiB/s
|
| [0 files][428.6 MiB/480.7 MiB] 10.0 MiB/s
/
-
- [0 files][438.8 MiB/480.7 MiB] 10.0 MiB/s
\
|
| [0 files][448.9 MiB/480.7 MiB] 10.0 MiB/s
/
/ [0 files][459.2 MiB/480.7 MiB] 10.3 MiB/s
-
\
\ [0 files][469.2 MiB/480.7 MiB] 10.1 MiB/s
|
/
/ [0 files][478.5 MiB/480.7 MiB] 9.9 MiB/s
/ [1 files][480.7 MiB/480.7 MiB] 9.3 MiB/s
Operation completed over 1 objects/480.7 MiB.
# Reduce the metadata to the selected columns
for(currname in names(metabolomics_processed_datasets)){
curr_data = metabolomics_processed_datasets[[currname]]$normalized_data[[1]]
# organize the metadata
curr_meta = merged_dmaqc_data[colnames(curr_data),
union(biospec_cols,differential_analysis_cols)]
# remove metadata variables with too many NAs
na_counts = apply(is.na(curr_meta),2,sum)
curr_meta = curr_meta[,na_counts/nrow(curr_meta) < 0.1]
metabolomics_processed_datasets[[currname]]$sample_meta_parsed = curr_meta
}
Log-transform: effect on differential analysis in targeted data
Untargeted data are typically log-transformed and analyzed using linear models. On the other hand, concentration data are sometimes analyzed with the same type of models but using the original data. This raises a problem if we wish to compare exact statistics from these data. In this section we perform residual analysis for single metabolites. Our goal is to identify if concentration data behaves “normally” when not log-transformed. The analysis below examines the residuals of the data after fitting linear models for each metabolite, adjusting for freeze time and sex. We then compare the results with and without the log-transformation, counting the number of metabolites with a significant evidence for non-normally distributed residuals.
# check for normality using the Kolmogorov-Smirnov test
is_normal_test<-function(v){
if(sd(v,na.rm = T)==0){return(0)}
try({return(shapiro.test(v)$p.value)})
# The Shapiro test may fail if the sd of v is zero
return(ks.test(v,"pnorm",mean(v,na.rm=T),sd(v,na.rm = T))$p.value)
}
# go over the named datasets, get a logged and an unlogged version of
# the data, use these as inputs for the regression
residual_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
x_log = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
x_unlog = 2^x_log
# take the covariates, ignore distances
x_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
curr_covs = x_meta[,intersect(colnames(x_meta),biospec_cols[2])]
curr_covs = data.frame(curr_covs,
sex=x_meta$animal.registration.sex)
# get the lm objects
curr_models = list()
for(tp in unique(x_meta$animal.key.timepoint)){
res_log = apply(
x_log,1,
pass1a_simple_differential_abundance,
tps = x_meta$animal.key.timepoint,tp=tp,
is_control = x_meta$animal.key.is_control,
covs = curr_covs,return_model=T
)
res_unlog = apply(
x_unlog,1,
pass1a_simple_differential_abundance,
tps = x_meta$animal.key.timepoint,tp=tp,
is_control = x_meta$animal.key.is_control,
covs = curr_covs,return_model=T
)
is_norm = cbind(
sapply(res_log,function(x)is_normal_test(residuals(x))),
sapply(res_unlog,function(x)is_normal_test(residuals(x)))
)
colnames(is_norm) = c("log","not log")
curr_models[[as.character(tp)]] = is_norm
# # test a specific model
# ind = 246
# plot(x_unlog[ind,],x_log[ind,])
#
# resids_log = rstandard(res_log[[ind]])
# fit_log = res_log[[ind]]$fitted.values
# resids_unlog = rstandard(res_unlog[[ind]])
# fit_unlog = res_unlog[[ind]]$fitted.values
#
# plot(fit_log,resids_log)
# plot(fit_unlog,resids_unlog)
}
residual_analysis_results[[nn1]] = curr_models
}
# Is there a significant difference between the two options?
log_vs_unlog_summ_mat = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)
wilcox.test(y[,1],y[,2],paired = T,alternative = "g")$p.value))
# Count the number of non-normal metabolites
num_nonnormal_log = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)sum(y[,1]<0.05)))
num_nonnormal_log =
num_nonnormal_log[,order(colnames(num_nonnormal_log))]
num_nonnormal_unlog = sapply(residual_analysis_results,
function(x)sapply(x,
function(y)sum(y[,2]<0.05)))
num_nonnormal_unlog =
num_nonnormal_unlog[,order(colnames(num_nonnormal_unlog))]
library(corrplot)
par(mar = c(5,5,5,10))
normdiffs = t(num_nonnormal_log)- t(num_nonnormal_unlog)
corrplot(normdiffs,is.corr = F,tl.cex = 0.7)
Trageted vs. Untargeted: single metabolite comparison
Compute statistics for each dataset
Compare overlaps, effect sizes, and correlations within tissues. Compare targeted-untargeted pairs only. For differential analysis we use the same model as in the analysis above.
# Transform the data matrix to have compound names as row names.
# This requires removing rows without names and changing the rownames of
# the input matrix x.
# Also, do not assume that the row annotation orde fits the data necessarily
extract_by_comp_name_from_row_annot<-function(x,row_annot_x){
# get the column that has the row names of x or at least
# have the greatest intersection with it
int_sizes = apply(row_annot_x,2,function(x,y)length(intersect(x,y)),y=rownames(x))
ind = which(int_sizes==max(int_sizes,na.rm = T))[1]
# update the annotation table to have only rows that have
# a row in x and then update the rownames to be from the
# selected column
row_annot_x = row_annot_x[is.element(row_annot_x[,ind],set=rownames(x)),]
rownames(row_annot_x) = row_annot_x[,ind]
# we can now intersect x and the annotation using their row names
# and update x accordingly
shared = intersect(rownames(row_annot_x),rownames(x))
x = x[shared,]
row_annot_x = row_annot_x[shared,]
rownames(x) = row_annot_x$motrpac_comp_name
return(x)
}
tar_vs_untar_norm_pairs = list(
c("log2,imp","log2,imp,TMM"),
c("log2,imp","log2,imp"),
c("log2,imp","med,log,imp"),
c("imp,none","imp,none"),
c("imp,none","imp,TMM"),
c("imp,none","med,log,imp")
)
# The loop below is the core of the computations for comparing
# targeted and untargeted data when using known compound names
#
# For each normalization option (a pair from the list above) we compute
# all pairwise correlation matrices of the overlapping metabolites and
# the differential analysis results (again of the overlap).
# These objects are then used later for other quantitative comparisons
norm_method2comparison_results = list()
for(norm_pairs in tar_vs_untar_norm_pairs){
tar_norm_method = norm_pairs[1]
untar_norm_method = norm_pairs[2]
single_metabolite_corrs = list()
single_metabolite_de = c()
named2covered_shared_metabolites = list()
for(nn1 in names(metabolomics_processed_datasets)){
nn1_tissue = metabolomics_processed_datasets[[nn1]]$tissue
if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
single_metabolite_corrs[[nn1]] = list()
named2covered_shared_metabolites[[nn1]] = NULL
for(nn2 in names(metabolomics_processed_datasets)){
if(nn2 == nn1){next}
if(metabolomics_processed_datasets[[nn2]]$is_targeted){next}
nn2_tissue = metabolomics_processed_datasets[[nn2]]$tissue
nn2_dataset = paste(strsplit(nn2,split=",")[[1]][3:4],collapse = ",")
if(nn1_tissue!=nn2_tissue){next}
# get the numeric datasets and their annotation
x = metabolomics_processed_datasets[[nn1]]$normalized_data[[tar_norm_method]]
y = metabolomics_processed_datasets[[nn2]]$normalized_data[[untar_norm_method]]
row_annot_x = metabolomics_processed_datasets[[nn1]]$row_annot
row_annot_y = metabolomics_processed_datasets[[nn2]]$row_annot
# transform metabolite names to the motrpac comp name
x = extract_by_comp_name_from_row_annot(x,row_annot_x)
y = extract_by_comp_name_from_row_annot(y,row_annot_y)
# align the sample sets
bid_y = merged_dmaqc_data[colnames(y),"bid"]
bid_x = merged_dmaqc_data[colnames(x),"bid"]
# step 1: merge samples from the same BID
if(length(unique(bid_x))!=length(bid_x)){
x = aggregate_repeated_samples(x,bid_x)
}
else{
colnames(x) = bid_x
}
if(length(unique(bid_y))!=length(bid_y)){
y = aggregate_repeated_samples(y,bid_y)
}else{
colnames(y) = bid_y
}
# step 2: use the shared bio ids
shared_bids = as.character(intersect(colnames(y),colnames(x)))
x = as.matrix(x[,shared_bids])
y = as.matrix(y[,shared_bids])
# At this point x and y are over the same BIDs, now we add the metadata
y_meta =
unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
rownames(y_meta) = y_meta$bid
y_meta = y_meta[shared_bids,]
# If one dataset is log-transformed and the other is not
# transform back to the original values
is_tar_log = grepl("log",tar_norm_method)
is_untar_log = grepl("log",untar_norm_method)
if(is_tar_log && !is_untar_log){
x = 2^x
}
if(!is_tar_log && is_untar_log){
y = 2^y
}
# If data are not log-transformed then scale the rows
if(!is_tar_log || !is_untar_log){
x = t(scale(t(x),center = T,scale = T))
y = t(scale(t(y),center = T,scale = T))
}
# get the shared matebolites
shared_metabolites = intersect(rownames(x),rownames(y))
shared_metabolites = na.omit(shared_metabolites)
if(length(shared_metabolites)==0){next}
named2covered_shared_metabolites[[nn1]] = union(
named2covered_shared_metabolites[[nn1]],
shared_metabolites
)
# Compute the correlation matrices of the shared metabolites
if(length(shared_metabolites)>1){
corrs =cor(t(x[shared_metabolites,]),
t(y[shared_metabolites,]),method = "spearman")
}
else{
corrs = cor(x[shared_metabolites,],
y[shared_metabolites,],method = "spearman")
}
# take the covariates (ignore distances)
curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
curr_covs = data.frame(y_meta[,curr_cov_cols])
names(curr_covs) = curr_cov_cols
curr_covs$sex = y_meta$animal.registration.sex # add sex
# differential analysis
for(tp in unique(y_meta$animal.key.timepoint)){
curr_control_tp = NULL
# if(tp == 7 || tp == 4){curr_control_tp=7}
resx = t(apply(
matrix(x[shared_metabolites,],nrow=length(shared_metabolites)),1,
pass1a_simple_differential_abundance,
tps = y_meta$animal.key.timepoint,tp=tp,
is_control = y_meta$animal.key.is_control,
covs = curr_covs,return_model=F,
control_tp = curr_control_tp
))
resy = t(apply(
matrix(y[shared_metabolites,],nrow=length(shared_metabolites)),1,
pass1a_simple_differential_abundance,
tps = y_meta$animal.key.timepoint,tp=tp,
is_control = y_meta$animal.key.is_control,
covs = curr_covs,return_model=F,
control_tp = curr_control_tp
))
# Add dataset information, time point, tissue
# These are important annotations for our summary matrix
# called single_metabolite_de below
added_columns = matrix(cbind(
rep(nn1,length(shared_metabolites)),
rep(nn2,length(shared_metabolites)),
shared_metabolites,
rep(tp,length(shared_metabolites)),
rep(nn1_tissue,length(shared_metabolites))
),nrow=length(shared_metabolites))
resx = cbind(resx,rep(T,nrow(resx)))
colnames(resx)[ncol(resx)] = "is_targeted"
resy = cbind(resy,rep(F,nrow(resy)))
colnames(resy)[ncol(resy)] = "is_targeted"
if(nrow(resx)>1){
resx = cbind(added_columns[,-2],resx)
resy = cbind(added_columns[,-1],resy)
}
else{
resx = c(added_columns[,-2],resx)
resy = c(added_columns[,-1],resy)
}
single_metabolite_de = rbind(single_metabolite_de,resx)
single_metabolite_de = rbind(single_metabolite_de,resy)
}
single_metabolite_corrs[[nn1]][[nn2]] = corrs
}
}
# Reformat the differential analysis results for easier comparison later
single_metabolite_de = data.frame(single_metabolite_de)
names(single_metabolite_de) = c("dataset","metabolite","tp","tissue",
"Est","Std","Tstat","Pvalue","is_targeted")
for(col in names(single_metabolite_de)[-c(1:4)]){
single_metabolite_de[[col]] = as.numeric(
as.character(single_metabolite_de[[col]]))
}
for(col in names(single_metabolite_de)[1:4]){
single_metabolite_de[[col]] =
as.character(single_metabolite_de[[col]])
}
# Remove duplications
# Rounding the p-values - necessary for removing duplicates
# using the unique function
rownames(single_metabolite_de) = NULL
for(nn in names(single_metabolite_de)){
ndig = 5
if(grepl("pval",nn,ignore.case = T)){
ndig = 10
}
if(is.numeric(single_metabolite_de[[nn]])){
single_metabolite_de[[nn]] =
round(single_metabolite_de[[nn]],digits = ndig)
}
}
single_metabolite_de = unique(single_metabolite_de)
# Finally, store all the results for the current normalization pair
pair_name = paste(tar_norm_method,untar_norm_method,sep=";")
norm_method2comparison_results[[pair_name]] = list(
single_metabolite_de = single_metabolite_de,
single_metabolite_corrs = single_metabolite_corrs,
named2covered_shared_metabolites = named2covered_shared_metabolites
)
}
We next transform the data above into tables that contain data for each combination of metabolite, time point, and tissue. These are then used for different meta-analyses: (1) a simple random effects analysis, (2) random effects with a binary covariate indicating if a dataset is targeted or untargeted, (3) redo the RE model of (1) with the targeted data only, and (4) redo the RE model of (1) with the untargeted data only.
library(metafor)
for(pair_name in names(norm_method2comparison_results)){
meta_analysis_stats = list()
single_metabolite_de =
norm_method2comparison_results[[pair_name]]$single_metabolite_de
for(tissue in unique(single_metabolite_de$tissue)){
for(tp in unique(single_metabolite_de$tp)){
curr_subset = single_metabolite_de[
single_metabolite_de$tissue==tissue &
single_metabolite_de$tp==tp,]
for(metabolite in unique(curr_subset$metabolite)){
curr_met_data = curr_subset[
curr_subset$metabolite==metabolite,]
curr_met_data$var = curr_met_data$Std^2
re_model1 = NULL;re_model2=NULL
re_model_tar = NULL;re_model_untar = NULL
try({re_model1 = rma.uni(curr_met_data$Est,curr_met_data$var,method="FE")})
try({re_model2 = rma.mv(curr_met_data$Est,curr_met_data$var,
mods=curr_met_data$is_targeted,method="FE")})
try({re_model_tar = rma.uni(
curr_met_data[curr_met_data$is_targeted==1,"Est"],
curr_met_data[curr_met_data$is_targeted==1,"var"],
method="FE"
)})
try({re_model_untar = rma.uni(
curr_met_data[curr_met_data$is_targeted==0,"Est"],
curr_met_data[curr_met_data$is_targeted==0,"var"],
method="FE"
)})
meta_analysis_stats[[paste(metabolite,tissue,tp,sep=",")]] =
list(curr_met_data=curr_met_data,re_model1=re_model1,
re_model2 = re_model2,re_model_tar=re_model_tar,
re_model_untar = re_model_untar)
}
}
}
norm_method2comparison_results[[pair_name]]$meta_analysis_stats =
meta_analysis_stats
}
Targeted vs. Untargeted: compound overlap
We first plot the number and percentage of metabolites in the targeted datasets that are measured in at least one untargeted dataset.
library(ggplot2)
dataset2num_metabolites = sapply(metabolomics_processed_datasets,
function(x)nrow(x$sample_data))
named_dataset_coverage = sapply(named2covered_shared_metabolites,length)
named_dataset_coverage = data.frame(
name = names(named_dataset_coverage),
percentage = named_dataset_coverage /
dataset2num_metabolites[names(named_dataset_coverage)],
count = named_dataset_coverage,
total = dataset2num_metabolites[names(named_dataset_coverage)]
)
# add datasets with no coverage
all_targeted_datasets = names(metabolomics_processed_datasets)
all_targeted_datasets = all_targeted_datasets[!grepl("untar",all_targeted_datasets)]
zero_coverage_datasets = setdiff(all_targeted_datasets,
named_dataset_coverage$name)
zero_coverage_datasets = data.frame(
name = zero_coverage_datasets,
percentage = rep(0,length(zero_coverage_datasets)),
count = rep(0,length(zero_coverage_datasets)),
total = dataset2num_metabolites[zero_coverage_datasets]
)
named_dataset_coverage = rbind(named_dataset_coverage,
zero_coverage_datasets)
named_dataset_coverage =
named_dataset_coverage[order(as.character(named_dataset_coverage$name)),]
print(ggplot(named_dataset_coverage, aes(x=name, y=percentage)) +
geom_bar(stat = "identity",width=0.2) + coord_flip() +
geom_text(data=named_dataset_coverage,
aes(name, percentage+0.05, label=count),
position = position_dodge(width=0.9),
size=4) +
ggtitle("Targeted dataset: coverage by untargeted"))

Comparison results: normalization methods
Spearman correlations
Compare the normalization methods by their correlation distributions.
rep_correlations = c()
tissues = unique(sapply(metabolomics_processed_datasets,function(x)x$tissue))
for(pair_name in names(norm_method2comparison_results)){
single_metabolite_corrs =
norm_method2comparison_results[[pair_name]]$single_metabolite_corrs
for(tissue in tissues){
curr_datasets = names(single_metabolite_corrs)[
grepl(tissue,names(single_metabolite_corrs))
]
between_corrs = c()
for(tar_dataset in curr_datasets){
l = single_metabolite_corrs[[tar_dataset]]
between_corrs = c(between_corrs,unname(unlist(sapply(l,diag))))
}
rep_correlations = rbind(rep_correlations,
c(pair_name,tissue,mean(between_corrs,na.rm=T),sd(between_corrs,na.rm=T)))
}
}
rep_correlations = rep_correlations[!is.na(rep_correlations[,3]),]
rep_correlations = data.frame(
"NormMethod" = rep_correlations[,1],
"Tissue" = rep_correlations[,2],
"Mean" = as.numeric(rep_correlations[,3]),
"SD" = as.numeric(rep_correlations[,4])
)
rep_correlations = rep_correlations[order(rep_correlations$Tissue),]
print(
ggplot(rep_correlations, aes(x=Tissue, y=Mean, fill=NormMethod)) +
geom_bar(position=position_dodge(), stat="identity", colour='black') +
geom_errorbar(aes(ymin=Mean-SD, ymax=Mean+SD),na.rm=T,
width=.2,position=position_dodge(.9))
)

Differential analysis results
tissues
[1] "plasma" "gastrocnemius" "liver" "white_adipose" "heart"
Meta-analysis results
names(pvals_untar)[significant_in=="Untargeted"]
[1] "alanine,plasma,0" "histidine,plasma,0"
[3] "Car(10:0),plasma,0" "Car(14:2),plasma,0"
[5] "Car(16:0),plasma,0" "Car(4:0-OH),plasma,0"
[7] "Car(5:1),plasma,0" "Car(8:0),plasma,0"
[9] "Car(10:1),plasma,0" "Car(10:2),plasma,0"
[11] "Car(4:0),plasma,0" "carnitine,plasma,0"
[13] "glutamine,plasma,0" "lysine,plasma,0"
[15] "threonine,plasma,0" "sphingosine-1P (C18),plasma,0"
[17] "histidine,plasma,1" "Car(10:0),plasma,1"
[19] "Car(3:0),plasma,1" "Car(5:0) isomers,plasma,1"
[21] "Car(5:1),plasma,1" "Car(8:0),plasma,1"
[23] "Car(10:1),plasma,1" "Car(20:0),plasma,1"
[25] "beta-alanine,plasma,1" "glutamine,plasma,1"
[27] "tryptophan,plasma,1" "sphinganine (C18),plasma,1"
[29] "sphingosine-1P (C18),plasma,1" "cis-aconitate,plasma,1"
[31] "arginine,plasma,4" "Car(10:0),plasma,4"
[33] "Car(12:0),plasma,4" "Car(12:1),plasma,4"
[35] "Car(14:1),plasma,4" "Car(14:2),plasma,4"
[37] "Car(20:4),plasma,4" "Car(3:0),plasma,4"
[39] "Car(5:1),plasma,4" "Car(8:0),plasma,4"
[41] "Car(10:1),plasma,4" "beta-alanine,plasma,4"
[43] "taurine,plasma,4" "Cer(d34:1)>Cer(d18:1/16:0),plasma,4"
[45] "Cer(d42:2)>Cer(d18:1/24:1),plasma,4" "sphingosine-1P (C18),plasma,4"
[47] "Car(5:1),plasma,48" "taurine,plasma,48"
[49] "Cer(d34:1)>Cer(d18:1/16:0),plasma,48" "SM(d44:2),plasma,48"
[51] "cis-aconitate,plasma,48" "Car(10:0),plasma,7"
[53] "Car(18:0),plasma,7" "Car(10:2),plasma,7"
[55] "carnitine,plasma,7" "tryptophan,plasma,7"
[57] "histidine,plasma,24" "Car(5:1),plasma,24"
[59] "Car(10:2),plasma,24" "isoleucine,plasma,24"
[61] "taurine,plasma,24" "tryptophan,plasma,24"
[63] "Cer(d42:2)>Cer(d18:1/24:1),plasma,24" "Car(10:0),plasma,0.5"
[65] "Car(20:4),plasma,0.5" "Car(4:0-OH),plasma,0.5"
[67] "Car(5:1),plasma,0.5" "Car(6:0),plasma,0.5"
[69] "Car(8:0),plasma,0.5" "Car(10:1),plasma,0.5"
[71] "beta-alanine,plasma,0.5" "glutamine,plasma,0.5"
[73] "threonine,plasma,0.5" "fumarate,plasma,0.5"
[75] "succinate,plasma,0.5"
Comparison results: detailed analysis of the selected normalization
Spearman correlations
We examine the average correlation between the platforms (within tissues). Whenever two platforms share more than a single metabolite we plot both the average correlation between the same metabolites and between other metabolites. Adding the average correlation between platforms but with different metabolites is important as it gives some perspective to what a significant correlation is. That is, in many cases below, the average correlation may be greater than expected.
# Next examine the Spearman correlations between platforms
mean_abs<-function(x,...){return(mean(abs(x),...))}
sd_abs<-function(x,...){return(sd(abs(x),...))}
extract_diag_vs_non_diag<-function(corrs,func=mean,...){
if(length(corrs)==1){
return(c(same=func(corrs,...),other=NA))
}
same = func(diag(corrs),...)
other = func(
c(corrs[lower.tri(corrs,diag = F)]),...)
return(c(same=same,other=other))
}
single_metabolite_corrs =
norm_method2comparison_results$`log2,imp;log2,imp`$single_metabolite_corrs
for(tar_dataset in names(single_metabolite_corrs)){
l = single_metabolite_corrs[[tar_dataset]]
if(length(l)==0){next}
corr_info = as.data.frame(t(sapply(l, extract_diag_vs_non_diag)))
corr_sd = as.data.frame(t(sapply(l, extract_diag_vs_non_diag,func=sd)))
# shorten the row names
rownames(corr_info) = sapply(rownames(corr_info),
function(x)paste(strsplit(x,split=",")[[1]][3:4],collapse=","))
rownames(corr_sd) = rownames(corr_info)
corr_info$dataset = rownames(corr_info)
corr_sd$dataset = corr_info$dataset
corr_info = melt(corr_info)
corr_sd = melt(corr_sd)
corr_info$sd = corr_sd$value
print(
ggplot(corr_info, aes(x=dataset, y=value, fill=variable)) +
geom_bar(position=position_dodge(), stat="identity", colour='black') +
geom_errorbar(aes(ymin=value-sd, ymax=value+sd),na.rm=T,
width=.2,position=position_dodge(.9)) +
ggtitle(tar_dataset) + xlab("Untargeted dataset") + ylab("Spearman") +
labs(fill = "Pair type") +
theme(legend.position="top",legend.direction = "horizontal")
)
}








































Plot selected examples
Here are the results for lactate in plasma.
Example from a log2 dataset:

Example from a scaled dataset:
meta_analysis_stats =
norm_method2comparison_results$`imp,none;imp,none`$meta_analysis_stats
lact_res = meta_analysis_stats[
grepl("lact",names(meta_analysis_stats),ignore.case = T) &
grepl("plasma",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
curr_labels = gsub("plasma,","",
meta_analysis_stats[[lact_example]][[1]][,1])
forest(meta_analysis_stats[[lact_example]]$re_model1,
slab = curr_labels,
main = lact_example,xlab = "Log fc",
col = "blue",cex = 1.1)
}






We can now check the same analysis for liver:
lact_res = meta_analysis_stats[
grepl("lact",names(meta_analysis_stats),ignore.case = T) &
grepl("liver",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
curr_labels = gsub("liver_powder,","",
meta_analysis_stats[[lact_example]][[1]][,1])
forest(meta_analysis_stats[[lact_example]]$re_model1,
slab = curr_labels,
main = lact_example,xlab = "Log fc",
col = "blue",cex = 1.1)
}






From the plots above we take the most extreme examples and examine their forest plots.
meta_analysis_stats =
norm_method2comparison_results$`imp,none;imp,none`$meta_analysis_stats
# P-value for the difference between targeted and untargeted
targeted_diff_p =
sapply(meta_analysis_stats,function(x)x$re_model2$pval[2])
# P-values - targeted vs. untargeted
pvals_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$pval)
pvals_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$pval)
pvals_untar = unlist(pvals_untar[sapply(pvals_untar,length)>0])
agree_example = names(sample(which(pvals_tar< 1e-5 & pvals_untar < 1e-5 &
targeted_diff_p > 0.1))[1])
simplify_labels_for_forest<-function(s){
s = gsub(",untargeted","",s)
tissue = strsplit(s,split=",")[[1]][1]
s = gsub(paste(tissue,",",sep=""),"",s)
return(s)
}
forest(meta_analysis_stats[[agree_example]]$re_model1,
slab = simplify_labels_for_forest(
meta_analysis_stats[[agree_example]][[1]][,1]),
main = paste(agree_example,"significant in both, tar and untar agree",sep="\n"),
xlab = "Log fc",col = "blue")
agree_p_disagree_beta = names(sample(which(pvals_tar< 1e-5 & pvals_untar < 1e-5 &
targeted_diff_p < 0.001))[1])
forest(meta_analysis_stats[[agree_p_disagree_beta]]$re_model1,
slab = simplify_labels_for_forest(
meta_analysis_stats[[agree_p_disagree_beta]][[1]][,1]),
main = paste(agree_p_disagree_beta,
"significant in both, tar and untar disagree",sep="\n"),
xlab = "Log fc",col = "blue")
disagree_example1 = names(sample(which(pvals_tar< 1e-10 & pvals_untar >0.1))[1])
forest(meta_analysis_stats[[disagree_example1]]$re_model1,
slab = simplify_labels_for_forest(
meta_analysis_stats[[disagree_example1]][[1]][,1]),
main = paste(disagree_example1,
"significant targeted, tar and untar disagree",sep="\n"),
xlab = "Log fc",col = "blue")
disagree_example2 = names(sample(which(pvals_tar > 0.1 & pvals_untar < 1e-20))[1])
forest(meta_analysis_stats[[disagree_example2]]$re_model1,
slab = simplify_labels_for_forest(
meta_analysis_stats[[disagree_example2]][[1]][,1]),
main = paste(disagree_example2,
"significant in untargeted, tar and untar disagree",sep="\n"),
xlab = "Log fc",col = "blue")
Targeted vs. untargeted: comparison as a prediction task
Use 5-fold cross validation for analysis within tissues. For each pair of targeted and untargeted datasets from the same tissue, we use the untargeted data as the predictive features and all metabolites in the targeted datasets as the dependent variables. The code below uses feature selection and random forests to train the predictive models.
nfolds = 5
prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
nn1_tissue = strsplit(nn1,split=",")[[1]][1]
nn1_tissue = gsub("_powder","",nn1_tissue)
if(grepl("untargeted",nn1)){next}
for(nn2 in names(metabolomics_processed_datasets)){
if(nn2 == nn1){next}
if(!grepl("untargeted",nn2)){next}
nn2_tissue = strsplit(nn2,split=",")[[1]][1]
nn2_tissue = gsub("_powder","",nn2_tissue)
nn2_dataset = strsplit(nn2,split=",")[[1]][2]
if(nn1_tissue!=nn2_tissue){next}
print(paste("features from:",nn2))
print(paste("labels from:",nn1))
# get the numeric datasets and their annotation
y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
x = metabolomics_processed_datasets[[nn2]]$normalized_data[[1]]
# align the sample sets
bid_y = merged_dmaqc_data[colnames(y),"bid"]
bid_x = merged_dmaqc_data[colnames(x),"bid"]
# step 1: merge samples from the same BID
if(length(unique(bid_x))!=length(bid_x)){
x = aggregate_repeated_samples(x,bid_x)
}
else{
colnames(x) = bid_x
}
if(length(unique(bid_y))!=length(bid_y)){
y = aggregate_repeated_samples(y,bid_y)
}else{
colnames(y) = bid_y
}
# step 2: use the shared bio ids
shared_bids = as.character(intersect(colnames(y),colnames(x)))
x = t(as.matrix(x[,shared_bids]))
y = t(as.matrix(y[,shared_bids]))
# At this point x and y are over the same BIDs, now we add the metadata
y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
rownames(y_meta) = y_meta$bid
y_meta = y_meta[shared_bids,]
# take the covariates (ignore distances)
curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
curr_covs = data.frame(y_meta[,curr_cov_cols])
names(curr_covs) = curr_cov_cols
curr_covs$sex = y_meta$animal.registration.sex # add sex
# add the covariates into x
x = cbind(x,curr_covs)
# Run the regressions
folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
numFeatures = min(ncol(x),2000)
preds = c();real=c()
for(i in 1:ncol(y)){
if( i %% 10 == 0){print(paste("analyzing metabolite number:",i))}
y_i = y[,1]
i_preds = c();i_real=c()
for(j in 1:nfolds){
tr_x = x[folds!=j,]
tr_yi = y_i[folds!=j]
te_x = x[folds==j,]
te_y = y_i[folds==j]
# random forest
# model = randomForest(tr_yi,x=tr_x,ntree = 20)
# te_preds = predict(model,newdata = te_x)
model = feature_selection_wrapper(tr_x,tr_yi,
coeff_of_var,randomForest,
topK = numFeatures,ntree=50)
te_preds = predict(model,newdata = te_x)
i_preds = c(i_preds,te_preds)
i_real = c(i_real,te_y)
}
preds = cbind(preds,i_preds)
real = cbind(real,i_real)
}
colnames(preds) = colnames(y)
colnames(reals) = colnames(y)
currname = paste(nn1,nn2,sep=";")
prediction_analysis_results[[currname]] = list(
preds = preds,real=real
)
}
}
save_to_bucket(prediction_analysis_results,
file="tar_vs_untar_prediction_analysis_results.RData",
bucket = "gs://bic_data_analysis/pass1a/metabolomics/")
We now take the predicted and real values and estimate the prediction accuracy in different ways.
prediction_analysis_results = load_from_bucket(
"tar_vs_untar_prediction_analysis_results.RData",
"gs://bic_data_analysis/pass1a/metabolomics/",F)
prediction_analysis_results = prediction_analysis_results[[1]]
results_metrics = list()
for(nn in names(prediction_analysis_results)){
preds = prediction_analysis_results[[nn]]$preds
real = prediction_analysis_results[[nn]]$real
tar_name = strsplit(nn,split=";")[[1]][1]
untar_name = strsplit(nn,split=";")[[1]][2]
y = metabolomics_processed_datasets[[tar_name]]$normalized_data[[1]]
colnames(preds) = rownames(y)
colnames(real) = rownames(y)
tar_name = simplify_metab_dataset_name(tar_name)
untar_name = simplify_metab_dataset_name(untar_name)
currtissue = strsplit(tar_name,split=",")[[1]][1]
tar_name = gsub(paste(currtissue,",",sep=""),"",tar_name)
untar_name = gsub(paste(currtissue,",",sep=""),"",untar_name)
if(! currtissue %in% names(results_metrics)){
results_metrics[[currtissue]] = list()
}
if(! tar_name %in% names(results_metrics[[currtissue]])){
results_metrics[[currtissue]][[tar_name]] = list()
}
rhos = format(diag(cor(preds,real,method="spearman")),digits=3)
rhos = as.numeric(rhos)
SEs = colSums((preds-real)^2)
MSEs = SEs / nrow(preds)
RMSE = sqrt(MSEs)
rMSE = MSEs / apply(y,1,var)
CoVs = apply(y,1,sd) / apply(y,1,mean)
discCoVs = cut(CoVs,breaks = 2,ordered_result = T)
results_metrics[[currtissue]][[tar_name]][[untar_name]] = data.frame(
rhos,MSEs,RMSE,rMSE,CoVs,discCoVs
)
}
We now present a few summary plots.
for(tissue in names(results_metrics)){
for(tar in names(results_metrics[[tissue]])){
l = results_metrics[[tissue]][[tar]]
rho_vs_cv = c()
for(untar in names(l)){
m = l[[untar]][,c("rhos","discCoVs")] # take the current matrix
m = cbind(rep(untar,nrow(m)),m)
m$discCoVs = as.numeric(m$discCoVs)
rho_vs_cv = rbind(rho_vs_cv,m)
}
colnames(rho_vs_cv)[1] = "dataset"
boxplot(rhos~discCoVs:dataset,data=rho_vs_cv,las=2,
ylab="Spearman",xlab = "",ylim=c(0,1),
main = paste(tissue,tar,sep=","))
}
}
As additional references, we train below additional models. First, we check the prediction of naive models that use technical and clinical covariates only. Second, we use multi-task regression and deep learning models.
cov_prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
nn1_tissue = strsplit(nn1,split=",")[[1]][1]
nn1_tissue = gsub("_powder","",nn1_tissue)
if(grepl("untargeted",nn1)){next}
print(nn1)
y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
y_vials = colnames(y)
bid_y = merged_dmaqc_data[colnames(y),"bid"]
colnames(y) = bid_y
y = t(as.matrix(y))
if(ncol(y)>1000){next}
cov_cols = c("animal.registration.sex",
"acute.test.weight",
"acute.test.distance",
"animal.key.timepoint")
covs = merged_dmaqc_data[y_vials,cov_cols]
x = covs
# Run the regressions
folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
numFeatures = min(ncol(x),2000)
preds = c();real=c()
for(i in 1:ncol(y)){
y_i = y[,1]
i_preds = c();i_real=c()
for(j in 1:nfolds){
print(j)
tr_x = x[folds!=j,]
tr_yi = y_i[folds!=j]
te_x = x[folds==j,]
te_y = y_i[folds==j]
# random forest
model = randomForest(tr_yi,x=tr_x,ntree = 20)
te_preds = predict(model,newdata = te_x)
i_preds = c(i_preds,te_preds)
i_real = c(i_real,te_y)
}
preds = cbind(preds,i_preds)
real = cbind(real,i_real)
}
cov_prediction_analysis_results[[nn1]] = list(
preds = preds,real=real
)
}
# preds = c();real=c()
# for(j in 1:nfolds){
# tr_x = x[folds!=j,]
# tr_y = y[folds!=j,]
# te_x = x[folds==j,]
# te_y = y[folds==j,]
# model = MTL_wrapper(tr_x,tr_y,type="Regression", Regularization="L21")
# te_preds = predict(model,te_x)
# real = rbind(real,te_y)
# preds = rbind(preds,te_preds)
# }
# diag(cor(preds,real))
# Using PLS regression
# library(pls)
# pls_model = plsr(y~x,ncomp = 5,validation="LOO")
# eval = MSEP(pls_model)
#
# y_pca = prcomp(y)
# plot(y_pca)
# explained_var = y_pca$sdev^2/sum(y_pca$sdev^2)
# y_pca_matrix = y_pca$x[,1:10]
#
# # regress out sex, weight
#
# get_explained_variance_using_PCA(x,y)
# x = apply(x,2,regress_out,covs=covs)
# y = apply(y,2,regress_out,covs=covs)
# get_explained_variance_using_PCA(x,y)
LS0tCnRpdGxlOiAiQklDIG1ldGFib2xvbWljcyBkYXRhIGFuYWx5c2lzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCkluIHRoaXMgZG9jdW1lbnQgd2UgcHJlc2VudCB0aGUgam9pbnQgYW5hbHlzaXMgb2YgdGhlIFBBU1MxQSBtZXRhYm9sb21pY3MgZGF0YXNldHMuCgojIExvYWQgYWxsIGRhdGFzZXRzCgpMb2FkIHRoZSBkYXRhIGZyb20gdGhlIGNsb3VkLCBpbmNsdWRpbmc6IHBoZW5vdHlwaWMgZGF0YSwgbWV0YWJvbG9taWMgZGF0YXNldHMsIGFuZCBtZXRhYm9sb21pY3MgZGljdGlvbmFyeS4KCmBgYHtyLHJlc3VsdHM9J2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Kc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9zdXBlcnZpc2VkX25vcm1hbGl6YXRpb25fZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL3Vuc3VwZXJ2aXNlZF9ub3JtYWxpemF0aW9uX2Z1bmN0aW9ucy5SIikKc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9nY3BfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL2Fzc29jaWF0aW9uX2FuYWx5c2lzX21ldGhvZHMuUiIpCnNvdXJjZSgifi9EZXNrdG9wL3JlcG9zL21vdHJwYWMtYmljLW5vcm0tcWMvdG9vbHMvZGF0YV9hdXhfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjL3Rvb2xzL3ByZWRpY3Rpb25fbWxfdG9vbHMuUiIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIGZvciBjbGFzc2lmaWNhdGlvbiB0ZXN0cwoKIyBMb2FkIHRoZSBkbWFxYyBkYXRhCm1lcmdlZF9kbWFxY19kYXRhID0gIGxvYWRfZnJvbV9idWNrZXQoIm1lcmdlZF9kbWFxY19kYXRhMjAxOS0xMC0xNS5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvcGhlbm9fZG1hcWMvIixGKQptZXJnZWRfZG1hcWNfZGF0YSA9IG1lcmdlZF9kbWFxY19kYXRhW1sxXV0Kcm93bmFtZXMobWVyZ2VkX2RtYXFjX2RhdGEpID0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kbWFxY19kYXRhJHZpYWxfbGFiZWwpCiMgZGVmaW5lIHRoZSB0aXNzdWUgdmFyaWFibGUKbWVyZ2VkX2RtYXFjX2RhdGEkdGlzc3VlID0gbWVyZ2VkX2RtYXFjX2RhdGEkc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgZGVmaW5lIHRoZSB0aW1lIHRvIGZyZWV6ZSB2YXJpYWJsZQptZXJnZWRfZG1hcWNfZGF0YSR0aW1lX3RvX2ZyZWV6ZSA9IG1lcmdlZF9kbWFxY19kYXRhJGNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfZGVhdGhfdG9fY29sbGVjdF9taW4gKyAKICBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy50aW1lX2NvbGxlY3RfdG9fZnJlZXplX21pbgoKIyBjb2wgdGltZSB2cy4gY29udHJvbAojIGRmID0gZGF0YS5mcmFtZSgKIyAgIGJpZCA9IG1lcmdlZF9kbWFxY19kYXRhJGJpZCwKIyAgIGVkdGFfY29sX3RpbWUgPSBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSwKIyAgIHRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemUsCiMgICBpc19jb250cm9sID0gbWVyZ2VkX2RtYXFjX2RhdGEkYW5pbWFsLmtleS5pc19jb250cm9sLAojICAgdHAgPSBtZXJnZWRfZG1hcWNfZGF0YSRhbmltYWwua2V5LnRpbWVwb2ludCwKIyAgIHRpc3N1ZSA9IG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgKQojIGRmID0gdW5pcXVlKGRmKQojIGJveHBsb3QoZWR0YV9jb2xfdGltZS8zNjAwIH4gaXNfY29udHJvbCxkZikKIyBib3hwbG90KGVkdGFfY29sX3RpbWUvMzYwMCAtIHRwIH4gaXNfY29udHJvbCxkZikKIyB3aWxjb3gudGVzdChlZHRhX2NvbF90aW1lLzM2MDAgfiBpc19jb250cm9sLGRmKQoKIyBibG9vZCBmcmVlemUgdGltZXMKYmxvb2Rfc2FtcGxlcyA9IAogIG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uID09CiAgIkVEVEEgUGxhc21hIgpibG9vZF9mcmVlemVfdGltZSA9IAogIGFzLmRpZmZ0aW1lKG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3NpbmcudF9mcmVlemUsdW5pdHMgPSAibWlucyIpIC0KICBhcy5kaWZmdGltZShtZXJnZWRfZG1hcWNfZGF0YSRzcGVjaW1lbi5wcm9jZXNzaW5nLnRfZWR0YXNwaW4sdW5pdHM9Im1pbnMiKQpibG9vZF9mcmVlemVfdGltZSA9IGFzLm51bWVyaWMoYmxvb2RfZnJlZXplX3RpbWUpCnRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemVbYmxvb2Rfc2FtcGxlc10gPSAKICBibG9vZF9mcmVlemVfdGltZVtibG9vZF9zYW1wbGVzXQoKIyBMb2FkIG91ciBwYXJzZWQgbWV0YWJvbG9taWNzIGRhdGFzZXRzCm1ldGFib2xvbWljc19idWNrZXRfb2JqID0gbG9hZF9mcm9tX2J1Y2tldCgKICBmaWxlID0gIm1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNfcGFzczFhX2V4dGVybmFsMS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iKQptZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzID0gbWV0YWJvbG9taWNzX2J1Y2tldF9vYmokbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cwoKYGBgCkRlZmluZSB0aGUgdmFyaWFibGVzIHRvIGJlIGFkanVzdGVkIGZvcjoKCmBgYHtyfQpiaW9zcGVjX2NvbHMgPSBjKAogICJhY3V0ZS50ZXN0LmRpc3RhbmNlIiwKICAiY2FsY3VsYXRlZC52YXJpYWJsZXMudGltZV90b19mcmVlemUiLAogICMgImNhbGN1bGF0ZWQudmFyaWFibGVzLmVkdGFfY29sbF90aW1lIiwgIyBubyBuZWVkIC0gc2VlIGNvZGUgYWJvdmUgZm9yIGJsb29kCiAgImJpZCIgIyByZXF1aXJlZCBmb3IgbWF0Y2hpbmcgZGF0YXNldHMKICApCmRpZmZlcmVudGlhbF9hbmFseXNpc19jb2xzID0gYygKICAiYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXgiLAogICJhbmltYWwua2V5LnRpbWVwb2ludCIsCiAgImFuaW1hbC5rZXkuaXNfY29udHJvbCIKKQpwaXBlbGluZV9xY19jb2xzID0gYygic2FtcGxlX29yZGVyIikKYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiB2YXJpYW5jZQoKU29tZSBzaXRlcyBkbyBub3QgdXNlIHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb24gdGhlaXIgZGF0YXNldC4gSW4gdGhpcyBzZWN0aW9uIHdlIHBsb3QgdGhlIGNvZWZmaWNpZW50IG9mIHZhcmlhdGlvbiBhcyBhIGZ1bmN0aW9uIG9mIHRoZSBtZWFuIGluc3RlbnNpdHkuIFdlIHRha2UgYSBzaW5nbGUgZGF0YXNldCBhcyBhbiBleGFtcGxlIHRvIHNob3cgaG93IGxvZy10cmFuc2Zvcm1lZCBkYXRhIGhhdmUgcmVkdWNlZCBkZXBlbmRlbmN5IGFuZCBzbW9vdGhlciBwbG90cy4KCkFzIGFuIGFkZGl0aW9uYWwgYW5hbHlzaXMgd2UgYWxzbyBwbG90IHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgcGVyIG1ldGFib2xpdGUgYXMgYSBmdW5jdGlvbiBvZiBpdHMgbWVhbiBpbnRlbnNpdHkuIFdlIHNob3cgdGhhdCB3aGlsZSB0aGVyZSBpcyBoaWdoIGNvcnJlbGF0aW9uIHNvbWUgbWlzc2luZyB2YWx1ZXMgYXBwZWFyIGluIGZhaXJlbHkgaGlnaCBpbnRlbnNpdGllcy4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIGltcHV0YXRpb24gYXMgc29tZSBzaXRlcyB1c2Ugc29tZSBmaXhlZCBsb3cgdmFsdWUgaW5zdGVhZCBvZiBrbm4gaW1wdXRhdGlvbi4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCiMgUGxvdCBjdiB2cyBtZWFucwpsaWJyYXJ5KGdwbG90cykKZCA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbWyJ3aGl0ZV9hZGlwb3NlX3Bvd2RlcixtZXRhYl91X2hpbGljcG9zLHVubmFtZWQiXV0KZHggPSBkJHNhbXBsZV9kYXRhCkNvVjwtZnVuY3Rpb24oeCl7cmV0dXJuKHNkKHgsbmEucm0gPSBUKS9tZWFuKHgsbmEucm09VCkpfQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKQ29WcyA9IGFwcGx5KGR4LDEsQ29WKQppbmRzID0gIWlzLm5hKENvVnMpCmRmID0gZGF0YS5mcmFtZShNZWFuX2ludGVuc2l0eSA9IGRtZWFuc1tpbmRzXSxDb1YgPSBDb1ZzW2luZHNdKQpwbG90KENvVn5NZWFuX2ludGVuc2l0eSxkZixjZXg9MC41LHBjaD0yMCxtYWluPSJSYXcgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFJlcGVhdCBhZnRlciBsb2cyCmR4ID0gbG9nKDErZCRzYW1wbGVfZGF0YSxiYXNlPTIpCmRtZWFucyA9IGFwcGx5KGR4LDEsbWVhbixuYS5ybT1UKQpDb1ZzID0gYXBwbHkoZHgsMSxDb1YpCmluZHMgPSAhaXMubmEoQ29WcykKZGYgPSBkYXRhLmZyYW1lKE1lYW5faW50ZW5zaXR5ID0gZG1lYW5zW2luZHNdLENvViA9IENvVnNbaW5kc10pCnBsb3QoQ29Wfk1lYW5faW50ZW5zaXR5LGRmLGNleD0wLjUscGNoPTIwLG1haW49IkxvZzIgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFBsb3QgbnVtYmVyIG9mIE5BcyB2cyBpbnRlbnNpdHkgbWVhbgpkeCA9IGxvZygxK2Qkc2FtcGxlX2RhdGEsYmFzZT0yKQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKbnVtX25hcyA9IHJvd1N1bXMoaXMubmEoZHgpKQpkZiA9IGRhdGEuZnJhbWUoTnVtX05BcyA9IG51bV9uYXNbaW5kc10sTWVhbl9pbnRlbnNpdHkgPSBkbWVhbnNbaW5kc10pCnJobyA9IGNvcihkZiROdW1fTkFzLGRmJE1lYW5faW50ZW5zaXR5LG1ldGhvZD0ic3BlYXJtYW4iKQpyaG8gPSBmb3JtYXQocmhvLGRpZ2l0cz0yKQpwbG90KE51bV9OQXN+TWVhbl9pbnRlbnNpdHksZGYsY2V4PTAuNSxwY2g9MjAsCiAgICAgbWFpbj1wYXN0ZSgiU3BlYXJtYW46IixyaG8pKQoKCmBgYAoKIyBMb2FkIHRoZSBwcmVwcm9jZXNzZWQgYW5kIG5vcm1hbGl6ZWQgZGF0YQoKYGBge3J9Cm1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMgPSBsb2FkX2Zyb21fYnVja2V0KAogIGZpbGU9Im1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMxMTE4MjAxOS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iCilbWzFdXQoKIyBSZWR1Y2UgdGhlIG1ldGFkYXRhIHRvIHRoZSBzZWxlY3RlZCBjb2x1bW5zCmZvcihjdXJybmFtZSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgY3Vycl9kYXRhID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICMgb3JnYW5pemUgdGhlIG1ldGFkYXRhCiAgY3Vycl9tZXRhID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoY3Vycl9kYXRhKSwKICAgICAgICB1bmlvbihiaW9zcGVjX2NvbHMsZGlmZmVyZW50aWFsX2FuYWx5c2lzX2NvbHMpXQogICMgcmVtb3ZlIG1ldGFkYXRhIHZhcmlhYmxlcyB3aXRoIHRvbyBtYW55IE5BcwogIG5hX2NvdW50cyA9IGFwcGx5KGlzLm5hKGN1cnJfbWV0YSksMixzdW0pCiAgY3Vycl9tZXRhID0gY3Vycl9tZXRhWyxuYV9jb3VudHMvbnJvdyhjdXJyX21ldGEpIDwgMC4xXQogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX21ldGFfcGFyc2VkID0gY3Vycl9tZXRhCn0KYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgaW4gdGFyZ2V0ZWQgZGF0YQoKVW50YXJnZXRlZCBkYXRhIGFyZSB0eXBpY2FsbHkgbG9nLXRyYW5zZm9ybWVkIGFuZCBhbmFseXplZCB1c2luZyBsaW5lYXIgbW9kZWxzLiBPbiB0aGUgb3RoZXIgaGFuZCwgY29uY2VudHJhdGlvbiBkYXRhIGFyZSBzb21ldGltZXMgYW5hbHl6ZWQgd2l0aCB0aGUgc2FtZSB0eXBlIG9mIG1vZGVscyBidXQgdXNpbmcgdGhlIG9yaWdpbmFsIGRhdGEuIFRoaXMgcmFpc2VzIGEgcHJvYmxlbSBpZiB3ZSB3aXNoIHRvIGNvbXBhcmUgZXhhY3Qgc3RhdGlzdGljcyBmcm9tIHRoZXNlIGRhdGEuIEluIHRoaXMgc2VjdGlvbiB3ZSBwZXJmb3JtIHJlc2lkdWFsIGFuYWx5c2lzIGZvciBzaW5nbGUgbWV0YWJvbGl0ZXMuIE91ciBnb2FsIGlzIHRvIGlkZW50aWZ5IGlmIGNvbmNlbnRyYXRpb24gZGF0YSBiZWhhdmVzICJub3JtYWxseSIgd2hlbiBub3QgbG9nLXRyYW5zZm9ybWVkLiBUaGUgYW5hbHlzaXMgYmVsb3cgZXhhbWluZXMgdGhlIHJlc2lkdWFscyBvZiB0aGUgZGF0YSBhZnRlciBmaXR0aW5nIGxpbmVhciBtb2RlbHMgZm9yIGVhY2ggbWV0YWJvbGl0ZSwgYWRqdXN0aW5nIGZvciBmcmVlemUgdGltZSBhbmQgc2V4LiBXZSB0aGVuIGNvbXBhcmUgdGhlIHJlc3VsdHMgd2l0aCBhbmQgd2l0aG91dCB0aGUgbG9nLXRyYW5zZm9ybWF0aW9uLCBjb3VudGluZyB0aGUgbnVtYmVyIG9mIG1ldGFib2xpdGVzIHdpdGggYSBzaWduaWZpY2FudCBldmlkZW5jZSBmb3Igbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkIHJlc2lkdWFscy4gCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgY2hlY2sgZm9yIG5vcm1hbGl0eSB1c2luZyB0aGUgS29sbW9nb3Jvdi1TbWlybm92IHRlc3QKaXNfbm9ybWFsX3Rlc3Q8LWZ1bmN0aW9uKHYpewogIGlmKHNkKHYsbmEucm0gPSBUKT09MCl7cmV0dXJuKDApfQogIHRyeSh7cmV0dXJuKHNoYXBpcm8udGVzdCh2KSRwLnZhbHVlKX0pCiAgIyBUaGUgU2hhcGlybyB0ZXN0IG1heSBmYWlsIGlmIHRoZSBzZCBvZiB2IGlzIHplcm8KICByZXR1cm4oa3MudGVzdCh2LCJwbm9ybSIsbWVhbih2LG5hLnJtPVQpLHNkKHYsbmEucm0gPSBUKSkkcC52YWx1ZSkKfQojIGdvIG92ZXIgdGhlIG5hbWVkIGRhdGFzZXRzLCBnZXQgYSBsb2dnZWQgYW5kIGFuIHVubG9nZ2VkIHZlcnNpb24gb2YKIyB0aGUgZGF0YSwgdXNlIHRoZXNlIGFzIGlucHV0cyBmb3IgdGhlIHJlZ3Jlc3Npb24KcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgeF9sb2cgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIHhfdW5sb2cgPSAyXnhfbG9nCiAgCiAgIyB0YWtlIHRoZSBjb3ZhcmlhdGVzLCBpZ25vcmUgZGlzdGFuY2VzCiAgeF9tZXRhID0gdW5pcXVlKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9tZXRhX3BhcnNlZCkKICBjdXJyX2NvdnMgPSB4X21ldGFbLGludGVyc2VjdChjb2xuYW1lcyh4X21ldGEpLGJpb3NwZWNfY29sc1syXSldCiAgY3Vycl9jb3ZzID0gZGF0YS5mcmFtZShjdXJyX2NvdnMsCiAgICAgICAgICAgc2V4PXhfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCkKICAKICAjIGdldCB0aGUgbG0gb2JqZWN0cwogIGN1cnJfbW9kZWxzID0gbGlzdCgpCiAgZm9yKHRwIGluIHVuaXF1ZSh4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQpKXsKICAgICAgcmVzX2xvZyA9IGFwcGx5KAogICAgICAgIHhfbG9nLDEsCiAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgIHRwcyA9IHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCx0cD10cCwKICAgICAgICBpc19jb250cm9sID0geF9tZXRhJGFuaW1hbC5rZXkuaXNfY29udHJvbCwKICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1UCiAgICAgICkKICAgICAgcmVzX3VubG9nID0gYXBwbHkoCiAgICAgICAgeF91bmxvZywxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHhfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9VAogICAgICApCiAgICAgIGlzX25vcm0gPSBjYmluZCgKICAgICAgICBzYXBwbHkocmVzX2xvZyxmdW5jdGlvbih4KWlzX25vcm1hbF90ZXN0KHJlc2lkdWFscyh4KSkpLAogICAgICAgIHNhcHBseShyZXNfdW5sb2csZnVuY3Rpb24oeClpc19ub3JtYWxfdGVzdChyZXNpZHVhbHMoeCkpKQogICAgICApCiAgICAgIGNvbG5hbWVzKGlzX25vcm0pID0gYygibG9nIiwibm90IGxvZyIpCiAgICAgIGN1cnJfbW9kZWxzW1thcy5jaGFyYWN0ZXIodHApXV0gPSBpc19ub3JtCiAgICAgIAogICAgICAjICMgdGVzdCBhIHNwZWNpZmljIG1vZGVsCiAgICAgICMgaW5kID0gMjQ2CiAgICAgICMgcGxvdCh4X3VubG9nW2luZCxdLHhfbG9nW2luZCxdKQogICAgICAjIAogICAgICAjIHJlc2lkc19sb2cgPSByc3RhbmRhcmQocmVzX2xvZ1tbaW5kXV0pCiAgICAgICMgZml0X2xvZyA9IHJlc19sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyByZXNpZHNfdW5sb2cgPSByc3RhbmRhcmQocmVzX3VubG9nW1tpbmRdXSkKICAgICAgIyBmaXRfdW5sb2cgPSByZXNfdW5sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyAKICAgICAgIyBwbG90KGZpdF9sb2cscmVzaWRzX2xvZykKICAgICAgIyBwbG90KGZpdF91bmxvZyxyZXNpZHNfdW5sb2cpCiAgfQogIHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHNbW25uMV1dID0gY3Vycl9tb2RlbHMKfQoKIyBJcyB0aGVyZSBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIG9wdGlvbnM/CmxvZ192c191bmxvZ19zdW1tX21hdCA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KQogICAgICAgICAgd2lsY294LnRlc3QoeVssMV0seVssMl0scGFpcmVkID0gVCxhbHRlcm5hdGl2ZSA9ICJnIikkcC52YWx1ZSkpCgojIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLW5vcm1hbCBtZXRhYm9saXRlcwpudW1fbm9ubm9ybWFsX2xvZyA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KXN1bSh5WywxXTwwLjA1KSkpCm51bV9ub25ub3JtYWxfbG9nID0gCiAgbnVtX25vbm5vcm1hbF9sb2dbLG9yZGVyKGNvbG5hbWVzKG51bV9ub25ub3JtYWxfbG9nKSldCm51bV9ub25ub3JtYWxfdW5sb2cgPSBzYXBwbHkocmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cywKICAgIGZ1bmN0aW9uKHgpc2FwcGx5KHgsCiAgICAgICAgZnVuY3Rpb24oeSlzdW0oeVssMl08MC4wNSkpKQpudW1fbm9ubm9ybWFsX3VubG9nID0gCiAgbnVtX25vbm5vcm1hbF91bmxvZ1ssb3JkZXIoY29sbmFtZXMobnVtX25vbm5vcm1hbF91bmxvZykpXQoKbGlicmFyeShjb3JycGxvdCkKcGFyKG1hciA9IGMoNSw1LDUsMTApKQpub3JtZGlmZnMgPSB0KG51bV9ub25ub3JtYWxfbG9nKS0gdChudW1fbm9ubm9ybWFsX3VubG9nKQpjb3JycGxvdChub3JtZGlmZnMsaXMuY29yciA9IEYsdGwuY2V4ID0gMC43KQpgYGAKCgojIFRyYWdldGVkIHZzLiBVbnRhcmdldGVkOiBzaW5nbGUgbWV0YWJvbGl0ZSBjb21wYXJpc29uCgojIyBDb21wdXRlIHN0YXRpc3RpY3MgZm9yIGVhY2ggZGF0YXNldAoKQ29tcGFyZSBvdmVybGFwcywgZWZmZWN0IHNpemVzLCBhbmQgY29ycmVsYXRpb25zIHdpdGhpbiB0aXNzdWVzLiBDb21wYXJlIHRhcmdldGVkLXVudGFyZ2V0ZWQgcGFpcnMgb25seS4gRm9yIGRpZmZlcmVudGlhbCBhbmFseXNpcyB3ZSB1c2UgdGhlIHNhbWUgbW9kZWwgYXMgaW4gdGhlIGFuYWx5c2lzIGFib3ZlLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQoKIyBUcmFuc2Zvcm0gdGhlIGRhdGEgbWF0cml4IHRvIGhhdmUgY29tcG91bmQgbmFtZXMgYXMgcm93IG5hbWVzLgojIFRoaXMgcmVxdWlyZXMgcmVtb3Zpbmcgcm93cyB3aXRob3V0IG5hbWVzIGFuZCBjaGFuZ2luZyB0aGUgcm93bmFtZXMgb2YKIyB0aGUgaW5wdXQgbWF0cml4IHguCiMgQWxzbywgZG8gbm90IGFzc3VtZSB0aGF0IHRoZSByb3cgYW5ub3RhdGlvbiBvcmRlIGZpdHMgdGhlIGRhdGEgbmVjZXNzYXJpbHkKZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3Q8LWZ1bmN0aW9uKHgscm93X2Fubm90X3gpewogICMgZ2V0IHRoZSBjb2x1bW4gdGhhdCBoYXMgdGhlIHJvdyBuYW1lcyBvZiB4IG9yIGF0IGxlYXN0CiAgIyBoYXZlIHRoZSBncmVhdGVzdCBpbnRlcnNlY3Rpb24gd2l0aCBpdAogIGludF9zaXplcyA9IGFwcGx5KHJvd19hbm5vdF94LDIsZnVuY3Rpb24oeCx5KWxlbmd0aChpbnRlcnNlY3QoeCx5KSkseT1yb3duYW1lcyh4KSkKICBpbmQgPSB3aGljaChpbnRfc2l6ZXM9PW1heChpbnRfc2l6ZXMsbmEucm0gPSBUKSlbMV0KICAjIHVwZGF0ZSB0aGUgYW5ub3RhdGlvbiB0YWJsZSB0byBoYXZlIG9ubHkgcm93cyB0aGF0IGhhdmUgCiAgIyBhIHJvdyBpbiB4IGFuZCB0aGVuIHVwZGF0ZSB0aGUgcm93bmFtZXMgdG8gYmUgZnJvbSB0aGUgCiAgIyBzZWxlY3RlZCBjb2x1bW4KICByb3dfYW5ub3RfeCA9IHJvd19hbm5vdF94W2lzLmVsZW1lbnQocm93X2Fubm90X3hbLGluZF0sc2V0PXJvd25hbWVzKHgpKSxdCiAgcm93bmFtZXMocm93X2Fubm90X3gpID0gcm93X2Fubm90X3hbLGluZF0KICAjIHdlIGNhbiBub3cgaW50ZXJzZWN0IHggYW5kIHRoZSBhbm5vdGF0aW9uIHVzaW5nIHRoZWlyIHJvdyBuYW1lcwogICMgYW5kIHVwZGF0ZSB4IGFjY29yZGluZ2x5CiAgc2hhcmVkID0gaW50ZXJzZWN0KHJvd25hbWVzKHJvd19hbm5vdF94KSxyb3duYW1lcyh4KSkKICB4ID0geFtzaGFyZWQsXQogIHJvd19hbm5vdF94ID0gcm93X2Fubm90X3hbc2hhcmVkLF0KICByb3duYW1lcyh4KSA9IHJvd19hbm5vdF94JG1vdHJwYWNfY29tcF9uYW1lCiAgcmV0dXJuKHgpCn0KCnRhcl92c191bnRhcl9ub3JtX3BhaXJzID0gbGlzdCgKICBjKCJsb2cyLGltcCIsImxvZzIsaW1wLFRNTSIpLAogIGMoImxvZzIsaW1wIiwibG9nMixpbXAiKSwKICBjKCJsb2cyLGltcCIsIm1lZCxsb2csaW1wIiksCiAgYygiaW1wLG5vbmUiLCJpbXAsbm9uZSIpLAogIGMoImltcCxub25lIiwiaW1wLFRNTSIpLAogIGMoImltcCxub25lIiwibWVkLGxvZyxpbXAiKQopCgojIFRoZSBsb29wIGJlbG93IGlzIHRoZSBjb3JlIG9mIHRoZSBjb21wdXRhdGlvbnMgZm9yIGNvbXBhcmluZyAKIyB0YXJnZXRlZCBhbmQgdW50YXJnZXRlZCBkYXRhIHdoZW4gdXNpbmcga25vd24gY29tcG91bmQgbmFtZXMKIwojIEZvciBlYWNoIG5vcm1hbGl6YXRpb24gb3B0aW9uIChhIHBhaXIgZnJvbSB0aGUgbGlzdCBhYm92ZSkgd2UgY29tcHV0ZQojIGFsbCBwYWlyd2lzZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBvZiB0aGUgb3ZlcmxhcHBpbmcgbWV0YWJvbGl0ZXMgYW5kCiMgdGhlIGRpZmZlcmVudGlhbCBhbmFseXNpcyByZXN1bHRzIChhZ2FpbiBvZiB0aGUgb3ZlcmxhcCkuCiMgVGhlc2Ugb2JqZWN0cyBhcmUgdGhlbiB1c2VkIGxhdGVyIGZvciBvdGhlciBxdWFudGl0YXRpdmUgY29tcGFyaXNvbnMKbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzID0gbGlzdCgpCmZvcihub3JtX3BhaXJzIGluIHRhcl92c191bnRhcl9ub3JtX3BhaXJzKXsKICB0YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzFdCiAgdW50YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzJdCiAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBsaXN0KCkKICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IGMoKQogIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbGlzdCgpCiAgZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBubjFfdGlzc3VlID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kdGlzc3VlCiAgICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbbm4xXV0gPSBsaXN0KCkKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzW1tubjFdXSA9IE5VTEwKICAgIGZvcihubjIgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogICAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgICBpZihtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRpc190YXJnZXRlZCl7bmV4dH0KICAgICAgbm4yX3Rpc3N1ZSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHRpc3N1ZQogICAgICBubjJfZGF0YXNldCA9IHBhc3RlKHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMzo0XSxjb2xsYXBzZSA9ICIsIikKICAgICAgaWYobm4xX3Rpc3N1ZSE9bm4yX3Rpc3N1ZSl7bmV4dH0KICAgICAgIyBnZXQgdGhlIG51bWVyaWMgZGF0YXNldHMgYW5kIHRoZWlyIGFubm90YXRpb24KICAgICAgeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJG5vcm1hbGl6ZWRfZGF0YVtbdGFyX25vcm1fbWV0aG9kXV0KICAgICAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJG5vcm1hbGl6ZWRfZGF0YVtbdW50YXJfbm9ybV9tZXRob2RdXQogICAgICByb3dfYW5ub3RfeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHJvd19hbm5vdAogICAgICByb3dfYW5ub3RfeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHJvd19hbm5vdAogICAgICAjIHRyYW5zZm9ybSBtZXRhYm9saXRlIG5hbWVzIHRvIHRoZSBtb3RycGFjIGNvbXAgbmFtZQogICAgICB4ID0gZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3QoeCxyb3dfYW5ub3RfeCkKICAgICAgeSA9IGV4dHJhY3RfYnlfY29tcF9uYW1lX2Zyb21fcm93X2Fubm90KHkscm93X2Fubm90X3kpCiAgICAgICMgYWxpZ24gdGhlIHNhbXBsZSBzZXRzCiAgICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICAgIGJpZF94ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeCksImJpZCJdICAgIAogICAgICAjIHN0ZXAgMTogbWVyZ2Ugc2FtcGxlcyBmcm9tIHRoZSBzYW1lIEJJRAogICAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICAgIHggPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh4LGJpZF94KQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgY29sbmFtZXMoeCkgPSBiaWRfeAogICAgICB9CiAgICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgICAgeSA9IGFnZ3JlZ2F0ZV9yZXBlYXRlZF9zYW1wbGVzKHksYmlkX3kpCiAgICAgIH1lbHNlewogICAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgICAgfQogICAgICAjIHN0ZXAgMjogdXNlIHRoZSBzaGFyZWQgYmlvIGlkcwogICAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgICB4ID0gYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkKICAgICAgeSA9IGFzLm1hdHJpeCh5WyxzaGFyZWRfYmlkc10pCiAgICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICAgIHlfbWV0YSA9IAogICAgICAgIHVuaXF1ZShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRzYW1wbGVfbWV0YV9wYXJzZWQpCiAgICAgIHJvd25hbWVzKHlfbWV0YSkgPSB5X21ldGEkYmlkCiAgICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAgIAogICAgICAjIElmIG9uZSBkYXRhc2V0IGlzIGxvZy10cmFuc2Zvcm1lZCBhbmQgdGhlIG90aGVyIGlzIG5vdAogICAgICAjIHRyYW5zZm9ybSBiYWNrIHRvIHRoZSBvcmlnaW5hbCB2YWx1ZXMKICAgICAgaXNfdGFyX2xvZyA9IGdyZXBsKCJsb2ciLHRhcl9ub3JtX21ldGhvZCkKICAgICAgaXNfdW50YXJfbG9nID0gZ3JlcGwoImxvZyIsdW50YXJfbm9ybV9tZXRob2QpCiAgICAgIGlmKGlzX3Rhcl9sb2cgJiYgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IDJeeAogICAgICB9CiAgICAgIGlmKCFpc190YXJfbG9nICYmIGlzX3VudGFyX2xvZyl7CiAgICAgICAgeSA9IDJeeQogICAgICB9CiAgICAKICAgICAgIyBJZiBkYXRhIGFyZSBub3QgbG9nLXRyYW5zZm9ybWVkIHRoZW4gc2NhbGUgdGhlIHJvd3MKICAgICAgaWYoIWlzX3Rhcl9sb2cgfHwgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IHQoc2NhbGUodCh4KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgICAgeSA9IHQoc2NhbGUodCh5KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgIH0KICAgIAogICAgICAjIGdldCB0aGUgc2hhcmVkIG1hdGVib2xpdGVzCiAgICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IGludGVyc2VjdChyb3duYW1lcyh4KSxyb3duYW1lcyh5KSkKICAgICAgc2hhcmVkX21ldGFib2xpdGVzID0gbmEub21pdChzaGFyZWRfbWV0YWJvbGl0ZXMpCiAgICAgIGlmKGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpPT0wKXtuZXh0fQogICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0gPSB1bmlvbigKICAgICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0sCiAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzCiAgICAgICkKICAgIAogICAgICAjIENvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIG1hdHJpY2VzIG9mIHRoZSBzaGFyZWQgbWV0YWJvbGl0ZXMKICAgICAgaWYobGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcyk+MSl7CiAgICAgICAgICBjb3JycyA9Y29yKHQoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSksCiAgICAgICAgICAgICAgICB0KHlbc2hhcmVkX21ldGFib2xpdGVzLF0pLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICAgIGNvcnJzID0gY29yKHhbc2hhcmVkX21ldGFib2xpdGVzLF0sCiAgICAgICAgICAgICAgICB5W3NoYXJlZF9tZXRhYm9saXRlcyxdLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgIAogICAgICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMgKGlnbm9yZSBkaXN0YW5jZXMpCiAgICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICAgIGN1cnJfY292cyA9IGRhdGEuZnJhbWUoeV9tZXRhWyxjdXJyX2Nvdl9jb2xzXSkKICAgICAgbmFtZXMoY3Vycl9jb3ZzKSA9IGN1cnJfY292X2NvbHMKICAgICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgIAogICAgICAjIGRpZmZlcmVudGlhbCBhbmFseXNpcwogICAgICBmb3IodHAgaW4gdW5pcXVlKHlfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICAgIGN1cnJfY29udHJvbF90cCA9IE5VTEwKICAgICAgICAjIGlmKHRwID09IDcgfHwgdHAgPT0gNCl7Y3Vycl9jb250cm9sX3RwPTd9CiAgICAgICAgcmVzeCA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgcmVzeSA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgIyBBZGQgZGF0YXNldCBpbmZvcm1hdGlvbiwgdGltZSBwb2ludCwgdGlzc3VlCiAgICAgICAgIyBUaGVzZSBhcmUgaW1wb3J0YW50IGFubm90YXRpb25zIGZvciBvdXIgc3VtbWFyeSBtYXRyaXgKICAgICAgICAjIGNhbGxlZCBzaW5nbGVfbWV0YWJvbGl0ZV9kZSBiZWxvdwogICAgICAgIGFkZGVkX2NvbHVtbnMgPSBtYXRyaXgoY2JpbmQoCiAgICAgICAgICByZXAobm4xLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjIsbGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcykpLAogICAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzLAogICAgICAgICAgcmVwKHRwLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjFfdGlzc3VlLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKQogICAgICAgICksbnJvdz1sZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKSkKICAgICAgICByZXN4ID0gY2JpbmQocmVzeCxyZXAoVCxucm93KHJlc3gpKSkKICAgICAgICBjb2xuYW1lcyhyZXN4KVtuY29sKHJlc3gpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICByZXN5ID0gY2JpbmQocmVzeSxyZXAoRixucm93KHJlc3kpKSkKICAgICAgICBjb2xuYW1lcyhyZXN5KVtuY29sKHJlc3kpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICBpZihucm93KHJlc3gpPjEpewogICAgICAgICAgcmVzeCA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0yXSxyZXN4KQogICAgICAgICAgcmVzeSA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0xXSxyZXN5KQogICAgICAgIH0KICAgICAgICBlbHNlewogICAgICAgICAgcmVzeCA9IGMoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgICByZXN5ID0gYyhhZGRlZF9jb2x1bW5zWywtMV0scmVzeSkKICAgICAgIH0KICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHJiaW5kKHNpbmdsZV9tZXRhYm9saXRlX2RlLHJlc3gpCiAgICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSByYmluZChzaW5nbGVfbWV0YWJvbGl0ZV9kZSxyZXN5KQogICAgICB9CiAgICAgIHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1tubjFdXVtbbm4yXV0gPSBjb3JycwogICAgfQogIH0KCiAgIyBSZWZvcm1hdCB0aGUgZGlmZmVyZW50aWFsIGFuYWx5c2lzIHJlc3VsdHMgZm9yIGVhc2llciBjb21wYXJpc29uIGxhdGVyCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSBkYXRhLmZyYW1lKHNpbmdsZV9tZXRhYm9saXRlX2RlKQogIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IGMoImRhdGFzZXQiLCJtZXRhYm9saXRlIiwidHAiLCJ0aXNzdWUiLAogICAgIkVzdCIsIlN0ZCIsIlRzdGF0IiwiUHZhbHVlIiwiaXNfdGFyZ2V0ZWQiKQogIGZvcihjb2wgaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpWy1jKDE6NCldKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IGFzLm51bWVyaWMoCiAgICAgIGFzLmNoYXJhY3RlcihzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbY29sXV0pKQogIH0KICBmb3IoY29sIGluIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKVsxOjRdKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IAogICAgICBhcy5jaGFyYWN0ZXIoc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dKQogIH0KICAjIFJlbW92ZSBkdXBsaWNhdGlvbnMKICAjIFJvdW5kaW5nIHRoZSBwLXZhbHVlcyAtIG5lY2Vzc2FyeSBmb3IgcmVtb3ZpbmcgZHVwbGljYXRlcwogICMgdXNpbmcgdGhlIHVuaXF1ZSBmdW5jdGlvbgogIHJvd25hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IE5VTEwKICBmb3Iobm4gaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpKXsKICAgIG5kaWcgPSA1CiAgICBpZihncmVwbCgicHZhbCIsbm4saWdub3JlLmNhc2UgPSBUKSl7CiAgICAgIG5kaWcgPSAxMAogICAgfQogICAgaWYoaXMubnVtZXJpYyhzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSkpewogICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSA9IAogICAgICByb3VuZChzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSxkaWdpdHMgPSBuZGlnKQogICAgfQogIH0KICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSkKICAKICAjIEZpbmFsbHksIHN0b3JlIGFsbCB0aGUgcmVzdWx0cyBmb3IgdGhlIGN1cnJlbnQgbm9ybWFsaXphdGlvbiBwYWlyCiAgcGFpcl9uYW1lID0gcGFzdGUodGFyX25vcm1fbWV0aG9kLHVudGFyX25vcm1fbWV0aG9kLHNlcD0iOyIpCiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSA9IGxpc3QoCiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHNpbmdsZV9tZXRhYm9saXRlX2RlLAogICAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycywKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXMKICApCn0KCmBgYAoKV2UgbmV4dCB0cmFuc2Zvcm0gdGhlIGRhdGEgYWJvdmUgaW50byB0YWJsZXMgdGhhdCBjb250YWluIGRhdGEgZm9yIGVhY2ggY29tYmluYXRpb24gb2YgbWV0YWJvbGl0ZSwgdGltZSBwb2ludCwgYW5kIHRpc3N1ZS4gVGhlc2UgYXJlIHRoZW4gdXNlZCBmb3IgZGlmZmVyZW50IG1ldGEtYW5hbHlzZXM6ICgxKSBhIHNpbXBsZSByYW5kb20gZWZmZWN0cyBhbmFseXNpcywgKDIpIHJhbmRvbSBlZmZlY3RzIHdpdGggYSBiaW5hcnkgY292YXJpYXRlIGluZGljYXRpbmcgaWYgYSBkYXRhc2V0IGlzIHRhcmdldGVkIG9yIHVudGFyZ2V0ZWQsICgzKSByZWRvIHRoZSBSRSBtb2RlbCBvZiAoMSkgd2l0aCB0aGUgdGFyZ2V0ZWQgZGF0YSBvbmx5LCBhbmQgKDQpIHJlZG8gdGhlIFJFIG1vZGVsIG9mICgxKSB3aXRoIHRoZSB1bnRhcmdldGVkIGRhdGEgb25seS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmxpYnJhcnkobWV0YWZvcikKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gbGlzdCgpCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSAKICAgIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0c1tbcGFpcl9uYW1lXV0kc2luZ2xlX21ldGFib2xpdGVfZGUKICBmb3IodGlzc3VlIGluIHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWUpKXsKICAgIGZvcih0cCBpbiB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUkdHApKXsKICAgICAgY3Vycl9zdWJzZXQgPSBzaW5nbGVfbWV0YWJvbGl0ZV9kZVsKICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWU9PXRpc3N1ZSAmCiAgICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0cD09dHAsXQogICAgICBmb3IobWV0YWJvbGl0ZSBpbiB1bmlxdWUoY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZSkpewogICAgICAgIGN1cnJfbWV0X2RhdGEgPSBjdXJyX3N1YnNldFsKICAgICAgICAgIGN1cnJfc3Vic2V0JG1ldGFib2xpdGU9PW1ldGFib2xpdGUsXQogICAgICAgIGN1cnJfbWV0X2RhdGEkdmFyID0gY3Vycl9tZXRfZGF0YSRTdGReMgogICAgICAgIHJlX21vZGVsMSA9IE5VTEw7cmVfbW9kZWwyPU5VTEwKICAgICAgICByZV9tb2RlbF90YXIgPSBOVUxMO3JlX21vZGVsX3VudGFyID0gTlVMTAogICAgICAgIHRyeSh7cmVfbW9kZWwxID0gcm1hLnVuaShjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhcixtZXRob2Q9IkZFIil9KQogICAgICAgIHRyeSh7cmVfbW9kZWwyID0gcm1hLm12KGN1cnJfbWV0X2RhdGEkRXN0LGN1cnJfbWV0X2RhdGEkdmFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RzPWN1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQsbWV0aG9kPSJGRSIpfSkKICAgICAgICB0cnkoe3JlX21vZGVsX3RhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTEsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0xLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgdHJ5KHtyZV9tb2RlbF91bnRhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTAsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0wLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbcGFzdGUobWV0YWJvbGl0ZSx0aXNzdWUsdHAsc2VwPSIsIildXSA9IAogICAgICAgICAgbGlzdChjdXJyX21ldF9kYXRhPWN1cnJfbWV0X2RhdGEscmVfbW9kZWwxPXJlX21vZGVsMSwKICAgICAgICAgICAgcmVfbW9kZWwyID0gcmVfbW9kZWwyLHJlX21vZGVsX3Rhcj1yZV9tb2RlbF90YXIsCiAgICAgICAgICAgIHJlX21vZGVsX3VudGFyID0gcmVfbW9kZWxfdW50YXIpCiAgICAgIH0KICAgIH0KICB9CiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRtZXRhX2FuYWx5c2lzX3N0YXRzID0KICAgIG1ldGFfYW5hbHlzaXNfc3RhdHMKfQoKYGBgCgojIyBUYXJnZXRlZCB2cy4gVW50YXJnZXRlZDogY29tcG91bmQgb3ZlcmxhcCAKCldlIGZpcnN0IHBsb3QgdGhlIG51bWJlciBhbmQgcGVyY2VudGFnZSBvZiBtZXRhYm9saXRlcyBpbiB0aGUgdGFyZ2V0ZWQgZGF0YXNldHMgdGhhdCBhcmUgbWVhc3VyZWQgaW4gYXQgbGVhc3Qgb25lIHVudGFyZ2V0ZWQgZGF0YXNldC4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpkYXRhc2V0Mm51bV9tZXRhYm9saXRlcyA9IHNhcHBseShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeClucm93KHgkc2FtcGxlX2RhdGEpKQpuYW1lZF9kYXRhc2V0X2NvdmVyYWdlID0gc2FwcGx5KG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzLGxlbmd0aCkKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IGRhdGEuZnJhbWUoCiAgbmFtZSA9IG5hbWVzKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UpLAogIHBlcmNlbnRhZ2UgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlIC8KICBkYXRhc2V0Mm51bV9tZXRhYm9saXRlc1tuYW1lcyhuYW1lZF9kYXRhc2V0X2NvdmVyYWdlKV0sCiAgY291bnQgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLAogIHRvdGFsID0gZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbbmFtZXMobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSldCikKIyBhZGQgZGF0YXNldHMgd2l0aCBubyBjb3ZlcmFnZQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBhbGxfdGFyZ2V0ZWRfZGF0YXNldHNbIWdyZXBsKCJ1bnRhciIsYWxsX3RhcmdldGVkX2RhdGFzZXRzKV0KemVyb19jb3ZlcmFnZV9kYXRhc2V0cyA9IHNldGRpZmYoYWxsX3RhcmdldGVkX2RhdGFzZXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpCnplcm9fY292ZXJhZ2VfZGF0YXNldHMgPSBkYXRhLmZyYW1lKAogIG5hbWUgPSB6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzLAogIHBlcmNlbnRhZ2UgPSByZXAoMCxsZW5ndGgoemVyb19jb3ZlcmFnZV9kYXRhc2V0cykpLAogIGNvdW50ID0gcmVwKDAsbGVuZ3RoKHplcm9fY292ZXJhZ2VfZGF0YXNldHMpKSwKICB0b3RhbCA9IGRhdGFzZXQybnVtX21ldGFib2xpdGVzW3plcm9fY292ZXJhZ2VfZGF0YXNldHNdCikKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IHJiaW5kKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHplcm9fY292ZXJhZ2VfZGF0YXNldHMpCm5hbWVkX2RhdGFzZXRfY292ZXJhZ2UgPSAKICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlW29yZGVyKGFzLmNoYXJhY3RlcihuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpKSxdCnByaW50KGdncGxvdChuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLCBhZXMoeD1uYW1lLCB5PXBlcmNlbnRhZ2UpKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoPTAuMikgKyBjb29yZF9mbGlwKCkgKwogIGdlb21fdGV4dChkYXRhPW5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsIAogICAgICAgICAgICBhZXMobmFtZSwgcGVyY2VudGFnZSswLjA1LCBsYWJlbD1jb3VudCksIAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksCiAgICAgICAgICAgIHNpemU9NCkgKyAKICBnZ3RpdGxlKCJUYXJnZXRlZCBkYXRhc2V0OiBjb3ZlcmFnZSBieSB1bnRhcmdldGVkIikpCgpgYGAKCiMjIENvbXBhcmlzb24gcmVzdWx0czogbm9ybWFsaXphdGlvbiBtZXRob2RzCgojIyMgU3BlYXJtYW4gY29ycmVsYXRpb25zCgpDb21wYXJlIHRoZSBub3JtYWxpemF0aW9uIG1ldGhvZHMgYnkgdGhlaXIgY29ycmVsYXRpb24gZGlzdHJpYnV0aW9ucy4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCnJlcF9jb3JyZWxhdGlvbnMgPSBjKCkKdGlzc3VlcyA9IHVuaXF1ZShzYXBwbHkobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyxmdW5jdGlvbih4KXgkdGlzc3VlKSkKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycyA9IAogICAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycwogIGZvcih0aXNzdWUgaW4gdGlzc3Vlcyl7CiAgICBjdXJyX2RhdGFzZXRzID0gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpWwogICAgICBncmVwbCh0aXNzdWUsbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpKQogICAgXQogICAgYmV0d2Vlbl9jb3JycyA9IGMoKQogICAgZm9yKHRhcl9kYXRhc2V0IGluIGN1cnJfZGF0YXNldHMpewogICAgICBsID0gc2luZ2xlX21ldGFib2xpdGVfY29ycnNbW3Rhcl9kYXRhc2V0XV0KICAgICAgYmV0d2Vlbl9jb3JycyA9IGMoYmV0d2Vlbl9jb3Jycyx1bm5hbWUodW5saXN0KHNhcHBseShsLGRpYWcpKSkpCiAgICB9CiAgICByZXBfY29ycmVsYXRpb25zID0gcmJpbmQocmVwX2NvcnJlbGF0aW9ucywKICAgICAgICAgIGMocGFpcl9uYW1lLHRpc3N1ZSxtZWFuKGJldHdlZW5fY29ycnMsbmEucm09VCksc2QoYmV0d2Vlbl9jb3JycyxuYS5ybT1UKSkpCiAgfQp9CnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zWyFpcy5uYShyZXBfY29ycmVsYXRpb25zWywzXSksXQpyZXBfY29ycmVsYXRpb25zID0gZGF0YS5mcmFtZSgKICAiTm9ybU1ldGhvZCIgPSByZXBfY29ycmVsYXRpb25zWywxXSwKICAiVGlzc3VlIiA9IHJlcF9jb3JyZWxhdGlvbnNbLDJdLAogICJNZWFuIiA9IGFzLm51bWVyaWMocmVwX2NvcnJlbGF0aW9uc1ssM10pLAogICJTRCIgPSBhcy5udW1lcmljKHJlcF9jb3JyZWxhdGlvbnNbLDRdKQopCnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zW29yZGVyKHJlcF9jb3JyZWxhdGlvbnMkVGlzc3VlKSxdCnByaW50KAogICAgZ2dwbG90KHJlcF9jb3JyZWxhdGlvbnMsIGFlcyh4PVRpc3N1ZSwgeT1NZWFuLCBmaWxsPU5vcm1NZXRob2QpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1NZWFuLVNELCB5bWF4PU1lYW4rU0QpLG5hLnJtPVQsIAogICAgICAgICAgICAgICAgICAgd2lkdGg9LjIscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKQopCgpgYGAKCiMjIyBEaWZmZXJlbnRpYWwgYW5hbHlzaXMgcmVzdWx0cwoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpwdGhyID0gMC4wMDEKZm9yKHRpc3N1ZSBpbiB0aXNzdWVzKXsKICBmb3IocGFpcl9uYW1lIGluIG5hbWVzKG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cykpewogICAgICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gCiAgICAgICAgICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHNbW3BhaXJfbmFtZV1dJG1ldGFfYW5hbHlzaXNfc3RhdHMKICAgICAgbWV0YV9hbmFseXNpc19zdGF0cyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgICAgICAgZ3JlcGwodGlzc3VlLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpKQogICAgICBdCiAgICAgIGlmKGxlbmd0aChtZXRhX2FuYWx5c2lzX3N0YXRzKT09MCl7bmV4dH0KICAgICAgbmFpdmVfYW5hbHlzaXNfdGFyID0gc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsCiAgICAgICAgZnVuY3Rpb24oeClzdW0oeCRjdXJyX21ldF9kYXRhWywiUHZhbHVlIl08cHRociAmCiAgICAgICAgICAgICAgICAgICAgIHgkY3Vycl9tZXRfZGF0YVssImlzX3RhcmdldGVkIl0pKQogICAgICBuYWl2ZV9hbmFseXNpc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLAogICAgICAgIGZ1bmN0aW9uKHgpc3VtKHgkY3Vycl9tZXRfZGF0YVssIlB2YWx1ZSJdPHB0aHIgJgogICAgICAgICAgICAgICAgICAgICAheCRjdXJyX21ldF9kYXRhWywiaXNfdGFyZ2V0ZWQiXSkpCiAgICAgIHRiID0gdGFibGUobmFpdmVfYW5hbHlzaXNfdGFyLG5haXZlX2FuYWx5c2lzX3VudGFyKQogICAgICBub25zaWdfaW5fYm90aCA9IHRiWzEsMV0KICAgICAgdGJbMSwxXSA9IDAKICAgICAgYmFycGxvdCh0KHRiKSwKICAgICAgICAgICAgICBsZWdlbmQ9VCx4bGFiPSJOdW1iZXIgb2YgdGFyZ2V0ZWQgZGF0YXNldHMgd2l0aCBwPDAuMDAxIiwKICAgICAgICAgICAgICB5bGFiID0gIm51bWJlciBvZiBtZXRhYm9saXRlcyIsCiAgICAgICAgICAgICAgbWFpbiA9IHBhc3RlKHRpc3N1ZSxwYWlyX25hbWUpKQogICAgICAKICAgICAgd3JpdGUudGFibGUoCiAgICAgICAgbmFtZXMod2hpY2gobmFpdmVfYW5hbHlzaXNfdGFyPjAgJiBuYWl2ZV9hbmFseXNpc191bnRhcj09MCkpLAogICAgICAgIHF1b3RlID0gRixyb3cubmFtZXMgPSBGCiAgICAgICkKICAgICAgd3JpdGUudGFibGUoCiAgICAgICAgbmFtZXMod2hpY2gobmFpdmVfYW5hbHlzaXNfdGFyPT0wICYgbmFpdmVfYW5hbHlzaXNfdW50YXI+MCkpLAogICAgICAgIHF1b3RlID0gRixyb3cubmFtZXMgPSBGCiAgICAgICkKICB9Cn0KYGBgCgoKIyMjIE1ldGEtYW5hbHlzaXMgcmVzdWx0cwoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KFZlbm5EaWFncmFtKQpyZXF1aXJlKGdyaWRFeHRyYSkKcHRociA9IDAuMDAxCnRpc3N1ZXMgPSB1bmlxdWUoc2FwcGx5KG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMsZnVuY3Rpb24oeCl4JHRpc3N1ZSkpCnRhcl92c191bnRhcl9jb3JyZWxhdGlvbnMgPSBjKCkKZm9yKHRpc3N1ZSBpbiB0aXNzdWVzKXsKICBmb3IocGFpcl9uYW1lIGluIG5hbWVzKG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cykpewogICAgbWV0YV9hbmFseXNpc19zdGF0cyA9IAogICAgICAgIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0c1tbcGFpcl9uYW1lXV0kbWV0YV9hbmFseXNpc19zdGF0cwogICAgbWV0YV9hbmFseXNpc19zdGF0cyAgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogICAgICAgIGdyZXBsKHRpc3N1ZSxuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSkKICAgICAgXQogICAgaWYoaXMubnVsbChtZXRhX2FuYWx5c2lzX3N0YXRzKSB8fCBsZW5ndGgobWV0YV9hbmFseXNpc19zdGF0cyk9PTApe25leHR9CiAgCiAgICAjIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKICAgIHRhcmdldGVkX2RpZmZfcCA9IAogICAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKICAgIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCiAgICBwdmFsc190YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdGFyJHB2YWwpCiAgICBwdmFsc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF91bnRhciRwdmFsKQogICAgcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCiAgICBzaWduaWZpY2FudF9pbiA9IHJlcCgiTm9uZSIsbGVuZ3RoKHB2YWxzX3VudGFyKSkKICAgIHNpZ25pZmljYW50X2luW3B2YWxzX3RhcjxwdGhyXSA9ICJUYXJnZXRlZCIKICAgIHNpZ25pZmljYW50X2luW3B2YWxzX3VudGFyPHB0aHJdID0gIlVudGFyZ2V0ZWQiCiAgICBzaWduaWZpY2FudF9pbltwdmFsc190YXI8cHRociAmIHB2YWxzX3VudGFyPHB0aHJdID0gIkJvdGgiCiAgICBzaWduaWZpY2FudF9kaWZmID0gdGFyZ2V0ZWRfZGlmZl9wPHB0aHIKICAgIHJobyA9IGNvcigtbG9nMTAocHZhbHNfdGFyKSwtbG9nMTAocHZhbHNfdW50YXIpLG1ldGhvZCA9ICJwZWFyc29uIikKICAgICMgQmV0YXMgLSB0YXJnZXRlZCB2cy4gdW50YXJnZXRlZAogICAgYmV0YXNfdGFyID0gCiAgICAgIHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkYmV0YVsxLDFdKQogICAgYmV0YXNfdW50YXIgPSAKICAgICAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsX3VudGFyJGJldGFbMSwxXSkKICAgIGJldGFzX3VudGFyID0gdW5saXN0KGJldGFzX3VudGFyW3NhcHBseShiZXRhc191bnRhcixsZW5ndGgpPjBdKQogICAgZGYgPSBkYXRhLmZyYW1lKAogICAgICB0YXJnZXRlZCA9IGJldGFzX3RhciwKICAgICAgdW50YXJnZXRlZCA9IGJldGFzX3VudGFyLAogICAgICBzaWduaWZpY2FudF9pbiA9IHNpZ25pZmljYW50X2luLAogICAgICBzaWduaWZpY2FudF9kaWZmID0gc2lnbmlmaWNhbnRfZGlmZgogICAgKQogICAgcmhvX2JldGEgPSBjb3IoYmV0YXNfdW50YXIsYmV0YXNfdGFyLG1ldGhvZCA9ICJwZWFyc29uIikKICAgIHJob3AgPSBjb3IudGVzdChiZXRhc191bnRhcixiZXRhc190YXIsbWV0aG9kID0gInBlYXJzb24iKSRwLnZhbHVlCiAgICBwcmludCgKICAgICAgZ2dwbG90KGRmLCBhZXMoeD10YXJnZXRlZCwgeT11bnRhcmdldGVkLAogICAgICAgICAgICAgICAgIHNoYXBlPXNpZ25pZmljYW50X2RpZmYsIGNvbG9yPXNpZ25pZmljYW50X2luKSkgKwogICAgICBnZW9tX3BvaW50KCkgKyBnZW9tX2FibGluZShzbG9wZT0xLGludGVyY2VwdCA9IDApICsgCiAgICAgIGdndGl0bGUocGFzdGUodGlzc3VlLCBwYWlyX25hbWUsCiAgICAgICAgICAgICAgICAgICAgImVmZmVjdHMsIHJobz06Iixmb3JtYXQocmhvX2JldGEsZGlnaXRzPTIpKSkKICAgICkKICAKICAgIHRhcl92c191bnRhcl9jb3JyZWxhdGlvbnMgPSByYmluZCh0YXJfdnNfdW50YXJfY29ycmVsYXRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGModGlzc3VlLHBhaXJfbmFtZSxyaG8scmhvX2JldGEpKQogICAjIGRyYXcgYSB2ZW5uIGRpYWdyYW0KICAgIGludGVyX2FyZWEgPSBzdW0oc2lnbmlmaWNhbnRfaW49PSJCb3RoIikKICAgIHRhcl9hcmVhID0gc3VtKHNpZ25pZmljYW50X2luPT0iVGFyZ2V0ZWQiKSArIGludGVyX2FyZWEKICAgIHVudGFyX2FyZWEgPSBzdW0oc2lnbmlmaWNhbnRfaW49PSJVbnRhcmdldGVkIikgKyBpbnRlcl9hcmVhCiAgICBzdWJ0ID0gcGFzdGUoIk5vdCBzaWduaWZpY2FudCBpbiBib3RoOiIsdGFibGUoc2lnbmlmaWNhbnRfaW4pWyJOb25lIl0pCiAgICAKICAgIHNzID0gcGFzdGUoc2FtcGxlKG5hbWVzKHB2YWxzX3VudGFyKVtzaWduaWZpY2FudF9pbj09IlRhcmdldGVkIl0pWzE6MTBdLGNvbGxhcHNlPSI7IikKICAgIHNzID0gZ3N1YihwYXN0ZSgiLCIsdGlzc3VlLHNlcD0iIiksIiIsc3MpCiAgICBzcwogICAgc3MgPSBwYXN0ZShzYW1wbGUobmFtZXMocHZhbHNfdW50YXIpW3NpZ25pZmljYW50X2luPT0iVW50YXJnZXRlZCJdKVsxOjEwXSxjb2xsYXBzZT0iOyIpCiAgICBzcyA9IGdzdWIocGFzdGUoIiwiLHRpc3N1ZSxzZXA9IiIpLCIiLHNzKQogICAgc3MKICAgIAogICAgdmVubmcgPSBkcmF3LnBhaXJ3aXNlLnZlbm4odGFyX2FyZWEsdW50YXJfYXJlYSxpbnRlcl9hcmVhLAogICAgICAgICAgICBjKCJUYXJnZXRlZCIsIlVudGFyZ2V0ZWQiKSxsdHkgPSByZXAoImJsYW5rIiwyKSwgCiAgICAgICAgICAgIGZpbGwgPSBjKCJwaW5rIiwgImN5YW4iKSwgYWxwaGEgPSByZXAoMC41LCAyKSwKICAgICAgICAgICAgY2F0LmRpc3QgPSByZXAoMC4wMSwgMiksaW5kPUYpCiAgICAjIGdyaWQubmV3cGFnZSgpCiAgICBncmlkLmFycmFuZ2UoZ1RyZWUoY2hpbGRyZW49dmVubmcpLCAKICAgICAgICAgICAgICAgICB0b3A9cGFzdGUodGlzc3VlLHBhaXJfbmFtZSksIGJvdHRvbT1zdWJ0KQogIH0KfQoKCmBgYAoKIyMgQ29tcGFyaXNvbiByZXN1bHRzOiBkZXRhaWxlZCBhbmFseXNpcyBvZiB0aGUgc2VsZWN0ZWQgbm9ybWFsaXphdGlvbgoKIyMjIFNwZWFybWFuIGNvcnJlbGF0aW9ucwoKV2UgZXhhbWluZSB0aGUgYXZlcmFnZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBwbGF0Zm9ybXMgKHdpdGhpbiB0aXNzdWVzKS4gV2hlbmV2ZXIgdHdvIHBsYXRmb3JtcyBzaGFyZSBtb3JlIHRoYW4gYSBzaW5nbGUgbWV0YWJvbGl0ZSB3ZSBwbG90IGJvdGggdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgc2FtZSBtZXRhYm9saXRlcyBhbmQgYmV0d2VlbiBvdGhlciBtZXRhYm9saXRlcy4gQWRkaW5nIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIGJldHdlZW4gcGxhdGZvcm1zIGJ1dCB3aXRoIGRpZmZlcmVudCBtZXRhYm9saXRlcyBpcyBpbXBvcnRhbnQgYXMgaXQgZ2l2ZXMgc29tZSBwZXJzcGVjdGl2ZSB0byB3aGF0IGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gaXMuIFRoYXQgaXMsIGluIG1hbnkgY2FzZXMgYmVsb3csIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIG1heSBiZSBncmVhdGVyIHRoYW4gZXhwZWN0ZWQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgTmV4dCBleGFtaW5lIHRoZSBTcGVhcm1hbiBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwbGF0Zm9ybXMKbWVhbl9hYnM8LWZ1bmN0aW9uKHgsLi4uKXtyZXR1cm4obWVhbihhYnMoeCksLi4uKSl9CnNkX2FiczwtZnVuY3Rpb24oeCwuLi4pe3JldHVybihzZChhYnMoeCksLi4uKSl9CmV4dHJhY3RfZGlhZ192c19ub25fZGlhZzwtZnVuY3Rpb24oY29ycnMsZnVuYz1tZWFuLC4uLil7CiAgaWYobGVuZ3RoKGNvcnJzKT09MSl7CiAgICByZXR1cm4oYyhzYW1lPWZ1bmMoY29ycnMsLi4uKSxvdGhlcj1OQSkpCiAgfQogIHNhbWUgPSBmdW5jKGRpYWcoY29ycnMpLC4uLikKICBvdGhlciA9IGZ1bmMoCiAgICBjKGNvcnJzW2xvd2VyLnRyaShjb3JycyxkaWFnID0gRildKSwuLi4pCiAgcmV0dXJuKGMoc2FtZT1zYW1lLG90aGVyPW90aGVyKSkKfQoKc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPQogIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cyRgbG9nMixpbXA7bG9nMixpbXBgJHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzCmZvcih0YXJfZGF0YXNldCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycykpewogIGwgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbdGFyX2RhdGFzZXRdXQogIGlmKGxlbmd0aChsKT09MCl7bmV4dH0KICBjb3JyX2luZm8gPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZykpKQogIGNvcnJfc2QgPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZyxmdW5jPXNkKSkpCiAgIyBzaG9ydGVuIHRoZSByb3cgbmFtZXMKICByb3duYW1lcyhjb3JyX2luZm8pID0gc2FwcGx5KHJvd25hbWVzKGNvcnJfaW5mbyksCiAgICAgIGZ1bmN0aW9uKHgpcGFzdGUoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bMzo0XSxjb2xsYXBzZT0iLCIpKQogIHJvd25hbWVzKGNvcnJfc2QpID0gcm93bmFtZXMoY29ycl9pbmZvKQogIGNvcnJfaW5mbyRkYXRhc2V0ID0gcm93bmFtZXMoY29ycl9pbmZvKQogIGNvcnJfc2QkZGF0YXNldCA9IGNvcnJfaW5mbyRkYXRhc2V0CiAgY29ycl9pbmZvID0gbWVsdChjb3JyX2luZm8pCiAgY29ycl9zZCA9IG1lbHQoY29ycl9zZCkKICBjb3JyX2luZm8kc2QgPSBjb3JyX3NkJHZhbHVlCiAgcHJpbnQoCiAgICBnZ3Bsb3QoY29ycl9pbmZvLCBhZXMoeD1kYXRhc2V0LCB5PXZhbHVlLCBmaWxsPXZhcmlhYmxlKSkgKwogICAgICBnZW9tX2Jhcihwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgpLCBzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nYmxhY2snKSArCiAgICAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49dmFsdWUtc2QsIHltYXg9dmFsdWUrc2QpLG5hLnJtPVQsIAogICAgICAgICAgICAgICAgICAgd2lkdGg9LjIscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKSArCiAgICBnZ3RpdGxlKHRhcl9kYXRhc2V0KSArIHhsYWIoIlVudGFyZ2V0ZWQgZGF0YXNldCIpICsgeWxhYigiU3BlYXJtYW4iKSArCiAgICAgIGxhYnMoZmlsbCA9ICJQYWlyIHR5cGUiKSArIAogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsbGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIikKICApCn0KYGBgCgojIyMgUGxvdCBzZWxlY3RlZCBleGFtcGxlcwoKSGVyZSBhcmUgdGhlIHJlc3VsdHMgZm9yIGxhY3RhdGUgaW4gcGxhc21hLgoKRXhhbXBsZSBmcm9tIGEgbG9nMiBkYXRhc2V0OgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQptZXRhX2FuYWx5c2lzX3N0YXRzID0gCiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzJGBsb2cyLGltcDttZWQsbG9nLGltcGAkbWV0YV9hbmFseXNpc19zdGF0cwpsYWN0X3JlcyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgZ3JlcGwoImxhY3QiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkgJgogICAgZ3JlcGwoInBsYXNtYSIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKQpdCmxhY3RfcmVzX2hvdXJzID0gc2FwcGx5KG5hbWVzKGxhY3RfcmVzKSwKICAgICAgICAgICAgZnVuY3Rpb24oeClhcy5udW1lcmljKHN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzNdKSkKbGFjdF9yZXMgPSBsYWN0X3Jlc1tvcmRlcihsYWN0X3Jlc19ob3VycyldCmZvcihsYWN0X2V4YW1wbGUgaW4gbmFtZXMobGFjdF9yZXMpWzE6Nl0pewogIGN1cnJfbGFiZWxzID0gZ3N1YigicGxhc21hLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dW1sxXV1bLDFdKQogIGZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXSRyZV9tb2RlbDEsCiAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gbGFjdF9leGFtcGxlLHhsYWIgPSAiTG9nIGZjIiwKICAgICAgIGNvbCA9ICJibHVlIixjZXggPSAxLjEpCn0KCmN1cnJfbGFiZWxzID0gZ3N1YigicGxhc21hLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbWyJnbHV0YW1hdGUscGxhc21hLDAiXV1bWzFdXVssMV0pCmN1cnJfbGFiZWxzID0gY3Vycl9sYWJlbHNbCiAgbWV0YV9hbmFseXNpc19zdGF0c1tbImdsdXRhbWF0ZSxwbGFzbWEsMCJdXVtbMV1dJGlzX3RhcmdldGVkPT0wXQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbImdsdXRhbWF0ZSxwbGFzbWEsMCJdXSRyZV9tb2RlbF91bnRhciwKICAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gImdsdXRhbWF0ZSxwbGFzbWEsMCIseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKCmBgYAoKRXhhbXBsZSBmcm9tIGEgc2NhbGVkIGRhdGFzZXQ6CgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMkYGltcCxub25lO2ltcCxub25lYCRtZXRhX2FuYWx5c2lzX3N0YXRzCmxhY3RfcmVzID0gbWV0YV9hbmFseXNpc19zdGF0c1sKICBncmVwbCgibGFjdCIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKSAmCiAgICBncmVwbCgicGxhc21hIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgY3Vycl9sYWJlbHMgPSBnc3ViKCJwbGFzbWEsIiwiIiwKICAgICAgICAgICAgICAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV1bWzFdXVssMV0pCiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBjdXJyX2xhYmVscywKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgpXZSBjYW4gbm93IGNoZWNrIHRoZSBzYW1lIGFuYWx5c2lzIGZvciBsaXZlcjoKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGFjdF9yZXMgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogIGdyZXBsKCJsYWN0IixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpICYKICAgIGdyZXBsKCJsaXZlciIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKQpdCmxhY3RfcmVzX2hvdXJzID0gc2FwcGx5KG5hbWVzKGxhY3RfcmVzKSwKICAgICAgICAgICAgZnVuY3Rpb24oeClhcy5udW1lcmljKHN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzNdKSkKbGFjdF9yZXMgPSBsYWN0X3Jlc1tvcmRlcihsYWN0X3Jlc19ob3VycyldCmZvcihsYWN0X2V4YW1wbGUgaW4gbmFtZXMobGFjdF9yZXMpWzE6Nl0pewogIGN1cnJfbGFiZWxzID0gZ3N1YigibGl2ZXJfcG93ZGVyLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dW1sxXV1bLDFdKQogIGZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXSRyZV9tb2RlbDEsCiAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gbGFjdF9leGFtcGxlLHhsYWIgPSAiTG9nIGZjIiwKICAgICAgIGNvbCA9ICJibHVlIixjZXggPSAxLjEpCn0KCmBgYAoKCjwhLS0gV2UgY3JlYXRlIHJlcG9ydCBieSB0aXNzdWUgd2l0aCBhbGwgZm9yZXN0IHBsb3RzIGFuZCBhbmFseXNpcyByZXN1bHRzLiAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIG1ldGFfYW5hbHlzaXNfdGlzc3VlcyA9IHNhcHBseShuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bMl0pIC0tPgo8IS0tIG1ldGFfYW5hbHlzaXNfbWV0YWJvbGl0ZXMgPSBzYXBwbHkobmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KXN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzFdKSAtLT4KCjwhLS0gZm9yKHRpc3N1ZSBpbiB1bmlxdWUobWV0YV9hbmFseXNpc190aXNzdWVzKSl7IC0tPgo8IS0tICAgcGRmKHBhc3RlKCJ+L0Rlc2t0b3AvIix0aXNzdWUsIi5wZGYiLHNlcD0iIikpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGBgYCAtLT4KCgoKCkZyb20gdGhlIHBsb3RzIGFib3ZlIHdlIHRha2UgdGhlIG1vc3QgZXh0cmVtZSBleGFtcGxlcyBhbmQgZXhhbWluZSB0aGVpciBmb3Jlc3QgcGxvdHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMkYGltcCxub25lO2ltcCxub25lYCRtZXRhX2FuYWx5c2lzX3N0YXRzCgojIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKdGFyZ2V0ZWRfZGlmZl9wID0gCiAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCnB2YWxzX3RhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkcHZhbCkKcHZhbHNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkcHZhbCkKcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCgoKYWdyZWVfZXhhbXBsZSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTUgJiBwdmFsc191bnRhciA8IDFlLTUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ZWRfZGlmZl9wID4gMC4xKSlbMV0pCnNpbXBsaWZ5X2xhYmVsc19mb3JfZm9yZXN0PC1mdW5jdGlvbihzKXsKICBzID0gZ3N1YigiLHVudGFyZ2V0ZWQiLCIiLHMpCiAgdGlzc3VlID0gc3Ryc3BsaXQocyxzcGxpdD0iLCIpW1sxXV1bMV0KICBzID0gZ3N1YihwYXN0ZSh0aXNzdWUsIiwiLHNlcD0iIiksIiIscykKICByZXR1cm4ocykKfQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfZXhhbXBsZV1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9leGFtcGxlXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShhZ3JlZV9leGFtcGxlLCJzaWduaWZpY2FudCBpbiBib3RoLCB0YXIgYW5kIHVudGFyIGFncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmFncmVlX3BfZGlzYWdyZWVfYmV0YSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTUgJiBwdmFsc191bnRhciA8IDFlLTUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ZWRfZGlmZl9wIDwgMC4wMDEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX3BfZGlzYWdyZWVfYmV0YV1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9wX2Rpc2FncmVlX2JldGFdXVtbMV1dWywxXSksCiAgbWFpbiA9IHBhc3RlKGFncmVlX3BfZGlzYWdyZWVfYmV0YSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IGluIGJvdGgsIHRhciBhbmQgdW50YXIgZGlzYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKZGlzYWdyZWVfZXhhbXBsZTEgPSBuYW1lcyhzYW1wbGUod2hpY2gocHZhbHNfdGFyPCAxZS0xMCAmIHB2YWxzX3VudGFyID4wLjEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShkaXNhZ3JlZV9leGFtcGxlMSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IHRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCgpkaXNhZ3JlZV9leGFtcGxlMiA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXIgPiAwLjEgJiBwdmFsc191bnRhciA8IDFlLTIwKSlbMV0pCmZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dW1sxXV1bLDFdKSwKICBtYWluID0gcGFzdGUoZGlzYWdyZWVfZXhhbXBsZTIsCiAgICAgICAgICAgICAgICJzaWduaWZpY2FudCBpbiB1bnRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmBgYAoKCiMgVGFyZ2V0ZWQgdnMuIHVudGFyZ2V0ZWQ6IGNvbXBhcmlzb24gYXMgYSBwcmVkaWN0aW9uIHRhc2sKClVzZSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBmb3IgYW5hbHlzaXMgd2l0aGluIHRpc3N1ZXMuIEZvciBlYWNoIHBhaXIgb2YgdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQgZGF0YXNldHMgZnJvbSB0aGUgc2FtZSB0aXNzdWUsIHdlIHVzZSB0aGUgdW50YXJnZXRlZCBkYXRhIGFzIHRoZSBwcmVkaWN0aXZlIGZlYXR1cmVzIGFuZCBhbGwgbWV0YWJvbGl0ZXMgaW4gdGhlIHRhcmdldGVkIGRhdGFzZXRzIGFzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzLiBUaGUgY29kZSBiZWxvdyB1c2VzIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCByYW5kb20gZm9yZXN0cyB0byB0cmFpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbHMuIAoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm5mb2xkcyA9IDUKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgZm9yKG5uMiBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgaWYoIWdyZXBsKCJ1bnRhcmdldGVkIixubjIpKXtuZXh0fQogICAgbm4yX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMV0KICAgIG5uMl90aXNzdWUgPSBnc3ViKCJfcG93ZGVyIiwiIixubjJfdGlzc3VlKQogICAgbm4yX2RhdGFzZXQgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzJdCiAgICBpZihubjFfdGlzc3VlIT1ubjJfdGlzc3VlKXtuZXh0fQogICAgcHJpbnQocGFzdGUoImZlYXR1cmVzIGZyb206IixubjIpKQogICAgcHJpbnQocGFzdGUoImxhYmVscyBmcm9tOiIsbm4xKSkKICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kbm9ybWFsaXplZF9kYXRhW1sxXV0KICAgIHggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IHQoYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkpCiAgICB5ID0gdChhcy5tYXRyaXgoeVssc2hhcmVkX2JpZHNdKSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgdGFrZSB0aGUgY292YXJpYXRlcyAoaWdub3JlIGRpc3RhbmNlcykKICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKHlfbWV0YVssY3Vycl9jb3ZfY29sc10pCiAgICBuYW1lcyhjdXJyX2NvdnMpID0gY3Vycl9jb3ZfY29scwogICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgICMgYWRkIHRoZSBjb3ZhcmlhdGVzIGludG8geAogICAgeCA9IGNiaW5kKHgsY3Vycl9jb3ZzKQogICAgCiAgICAjIFJ1biB0aGUgcmVncmVzc2lvbnMKICAgIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgICBwcmVkcyA9IGMoKTtyZWFsPWMoKQogICAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgICAgaWYoIGkgJSUgMTAgPT0gMCl7cHJpbnQocGFzdGUoImFuYWx5emluZyBtZXRhYm9saXRlIG51bWJlcjoiLGkpKX0KICAgICAgeV9pID0geVssMV0KICAgICAgaV9wcmVkcyA9IGMoKTtpX3JlYWw9YygpCiAgICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgICAgdHJfeWkgPSB5X2lbZm9sZHMhPWpdCiAgICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICAgIyByYW5kb20gZm9yZXN0CiAgICAgICAgIyBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgICAjIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgICBtb2RlbCA9IGZlYXR1cmVfc2VsZWN0aW9uX3dyYXBwZXIodHJfeCx0cl95aSwKICAgICAgICAgICAgICAgICAgIGNvZWZmX29mX3ZhcixyYW5kb21Gb3Jlc3QsCiAgICAgICAgICAgICAgICAgICB0b3BLID0gbnVtRmVhdHVyZXMsbnRyZWU9NTApCiAgICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICAgIGlfcHJlZHMgPSBjKGlfcHJlZHMsdGVfcHJlZHMpCiAgICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgICAgfQogICAgICBwcmVkcyA9IGNiaW5kKHByZWRzLGlfcHJlZHMpCiAgICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICAgIH0KICAgIGNvbG5hbWVzKHByZWRzKSA9IGNvbG5hbWVzKHkpCiAgICBjb2xuYW1lcyhyZWFscykgPSBjb2xuYW1lcyh5KQogICAgY3Vycm5hbWUgPSBwYXN0ZShubjEsbm4yLHNlcD0iOyIpCiAgICBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW2N1cnJuYW1lXV0gPSBsaXN0KAogICAgICBwcmVkcyA9IHByZWRzLHJlYWw9cmVhbAogICAgKQogIH0KfQpzYXZlX3RvX2J1Y2tldChwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgICAgICAgIGZpbGU9InRhcl92c191bnRhcl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMuUkRhdGEiLAogICAgICAgICAgICAgICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpCmBgYAoKV2Ugbm93IHRha2UgdGhlIHByZWRpY3RlZCBhbmQgcmVhbCB2YWx1ZXMgYW5kIGVzdGltYXRlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IGluIGRpZmZlcmVudCB3YXlzLiAKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLGV2YWw9RkFMU0UsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQoKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gIGxvYWRfZnJvbV9idWNrZXQoCiAgInRhcl92c191bnRhcl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMuUkRhdGEiLAogICAgImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iLEYpCnByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cyA9IHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbMV1dCgpyZXN1bHRzX21ldHJpY3MgPSBsaXN0KCkKZm9yKG5uIGluIG5hbWVzKHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cykpewogIHByZWRzID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHByZWRzCiAgcmVhbCA9IHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbbm5dXSRyZWFsCiAgdGFyX25hbWUgPSBzdHJzcGxpdChubixzcGxpdD0iOyIpW1sxXV1bMV0KICB1bnRhcl9uYW1lID0gc3Ryc3BsaXQobm4sc3BsaXQ9IjsiKVtbMV1dWzJdCiAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW3Rhcl9uYW1lXV0kbm9ybWFsaXplZF9kYXRhW1sxXV0KICBjb2xuYW1lcyhwcmVkcykgPSByb3duYW1lcyh5KQogIGNvbG5hbWVzKHJlYWwpID0gcm93bmFtZXMoeSkKICB0YXJfbmFtZSA9IHNpbXBsaWZ5X21ldGFiX2RhdGFzZXRfbmFtZSh0YXJfbmFtZSkKICB1bnRhcl9uYW1lID0gc2ltcGxpZnlfbWV0YWJfZGF0YXNldF9uYW1lKHVudGFyX25hbWUpCiAgY3VycnRpc3N1ZSA9IHN0cnNwbGl0KHRhcl9uYW1lLHNwbGl0PSIsIilbWzFdXVsxXQogIHRhcl9uYW1lID0gZ3N1YihwYXN0ZShjdXJydGlzc3VlLCIsIixzZXA9IiIpLCIiLHRhcl9uYW1lKQogIHVudGFyX25hbWUgPSBnc3ViKHBhc3RlKGN1cnJ0aXNzdWUsIiwiLHNlcD0iIiksIiIsdW50YXJfbmFtZSkKICBpZighIGN1cnJ0aXNzdWUgJWluJSBuYW1lcyhyZXN1bHRzX21ldHJpY3MpKXsKICAgIHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dID0gbGlzdCgpCiAgfQogIGlmKCEgdGFyX25hbWUgJWluJSBuYW1lcyhyZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXSkpewogICAgcmVzdWx0c19tZXRyaWNzW1tjdXJydGlzc3VlXV1bW3Rhcl9uYW1lXV0gPSBsaXN0KCkKICB9CiAgCiAgcmhvcyA9IGZvcm1hdChkaWFnKGNvcihwcmVkcyxyZWFsLG1ldGhvZD0ic3BlYXJtYW4iKSksZGlnaXRzPTMpCiAgcmhvcyA9IGFzLm51bWVyaWMocmhvcykKICBTRXMgPSBjb2xTdW1zKChwcmVkcy1yZWFsKV4yKQogIE1TRXMgPSBTRXMgLyBucm93KHByZWRzKQogIFJNU0UgPSBzcXJ0KE1TRXMpCiAgck1TRSA9IE1TRXMgLyBhcHBseSh5LDEsdmFyKQogIENvVnMgPSBhcHBseSh5LDEsc2QpIC8gYXBwbHkoeSwxLG1lYW4pCiAgZGlzY0NvVnMgPSBjdXQoQ29WcyxicmVha3MgPSAyLG9yZGVyZWRfcmVzdWx0ID0gVCkKICAKICByZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXVtbdGFyX25hbWVdXVtbdW50YXJfbmFtZV1dID0gZGF0YS5mcmFtZSgKICAgIHJob3MsTVNFcyxSTVNFLHJNU0UsQ29WcyxkaXNjQ29WcwogICkKfQoKYGBgCgpXZSBub3cgcHJlc2VudCBhIGZldyBzdW1tYXJ5IHBsb3RzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CmZvcih0aXNzdWUgaW4gbmFtZXMocmVzdWx0c19tZXRyaWNzKSl7CiAgZm9yKHRhciBpbiBuYW1lcyhyZXN1bHRzX21ldHJpY3NbW3Rpc3N1ZV1dKSl7CiAgICBsID0gcmVzdWx0c19tZXRyaWNzW1t0aXNzdWVdXVtbdGFyXV0KICAgIHJob192c19jdiA9IGMoKQogICAgZm9yKHVudGFyIGluIG5hbWVzKGwpKXsKICAgICAgbSA9IGxbW3VudGFyXV1bLGMoInJob3MiLCJkaXNjQ29WcyIpXSAjIHRha2UgdGhlIGN1cnJlbnQgbWF0cml4CiAgICAgIG0gPSBjYmluZChyZXAodW50YXIsbnJvdyhtKSksbSkKICAgICAgbSRkaXNjQ29WcyA9IGFzLm51bWVyaWMobSRkaXNjQ29WcykKICAgICAgcmhvX3ZzX2N2ID0gcmJpbmQocmhvX3ZzX2N2LG0pCiAgICB9CiAgICBjb2xuYW1lcyhyaG9fdnNfY3YpWzFdID0gImRhdGFzZXQiCiAgICBib3hwbG90KHJob3N+ZGlzY0NvVnM6ZGF0YXNldCxkYXRhPXJob192c19jdixsYXM9MiwKICAgICAgICAgICAgeWxhYj0iU3BlYXJtYW4iLHhsYWIgPSAiIix5bGltPWMoMCwxKSwKICAgICAgICAgICAgbWFpbiA9IHBhc3RlKHRpc3N1ZSx0YXIsc2VwPSIsIikpCiAgfQp9CgoKYGBgCgpBcyBhZGRpdGlvbmFsIHJlZmVyZW5jZXMsIHdlIHRyYWluIGJlbG93IGFkZGl0aW9uYWwgbW9kZWxzLiBGaXJzdCwgd2UgY2hlY2sgdGhlIHByZWRpY3Rpb24gb2YgbmFpdmUgbW9kZWxzIHRoYXQgdXNlIHRlY2huaWNhbCBhbmQgY2xpbmljYWwgY292YXJpYXRlcyBvbmx5LiBTZWNvbmQsIHdlIHVzZSBtdWx0aS10YXNrIHJlZ3Jlc3Npb24gYW5kIGRlZXAgbGVhcm5pbmcgbW9kZWxzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cgpjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgcHJpbnQobm4xKQogIHkgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIHlfdmlhbHMgPSBjb2xuYW1lcyh5KQogIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgY29sbmFtZXMoeSkgPSBiaWRfeQogIHkgPSB0KGFzLm1hdHJpeCh5KSkKICBpZihuY29sKHkpPjEwMDApe25leHR9CiAgY292X2NvbHMgPSBjKCJhbmltYWwucmVnaXN0cmF0aW9uLnNleCIsCiAgICAgICAgICAgICAiYWN1dGUudGVzdC53ZWlnaHQiLAogICAgICAgICAgICAgImFjdXRlLnRlc3QuZGlzdGFuY2UiLAogICAgICAgICAgICAgImFuaW1hbC5rZXkudGltZXBvaW50IikKICBjb3ZzID0gbWVyZ2VkX2RtYXFjX2RhdGFbeV92aWFscyxjb3ZfY29sc10KICB4ID0gY292cwogIAogICMgUnVuIHRoZSByZWdyZXNzaW9ucwogIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgbnVtRmVhdHVyZXMgPSBtaW4obmNvbCh4KSwyMDAwKQogIHByZWRzID0gYygpO3JlYWw9YygpCiAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgIHlfaSA9IHlbLDFdCiAgICBpX3ByZWRzID0gYygpO2lfcmVhbD1jKCkKICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgcHJpbnQoaikKICAgICAgdHJfeCA9IHhbZm9sZHMhPWosXQogICAgICB0cl95aSA9IHlfaVtmb2xkcyE9al0KICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICB0ZV95ID0geV9pW2ZvbGRzPT1qXQogICAgICAjIHJhbmRvbSBmb3Jlc3QKICAgICAgbW9kZWwgPSByYW5kb21Gb3Jlc3QodHJfeWkseD10cl94LG50cmVlID0gMjApCiAgICAgIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgaV9wcmVkcyA9IGMoaV9wcmVkcyx0ZV9wcmVkcykKICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgIH0KICAgIHByZWRzID0gY2JpbmQocHJlZHMsaV9wcmVkcykKICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICB9CiAgY292X3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbbm4xXV0gPSBsaXN0KAogICAgICBwcmVkcyA9IHByZWRzLHJlYWw9cmVhbAogICAgKQp9CgojIHByZWRzID0gYygpO3JlYWw9YygpCiMgZm9yKGogaW4gMTpuZm9sZHMpewojICAgdHJfeCA9IHhbZm9sZHMhPWosXQojICAgdHJfeSA9IHlbZm9sZHMhPWosXQojICAgdGVfeCA9IHhbZm9sZHM9PWosXQojICAgdGVfeSA9IHlbZm9sZHM9PWosXQojICAgbW9kZWwgPSBNVExfd3JhcHBlcih0cl94LHRyX3ksdHlwZT0iUmVncmVzc2lvbiIsIFJlZ3VsYXJpemF0aW9uPSJMMjEiKQojICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLHRlX3gpCiMgICByZWFsID0gcmJpbmQocmVhbCx0ZV95KQojICAgcHJlZHMgPSByYmluZChwcmVkcyx0ZV9wcmVkcykKIyB9CiMgZGlhZyhjb3IocHJlZHMscmVhbCkpCgojIFVzaW5nIFBMUyByZWdyZXNzaW9uCiMgbGlicmFyeShwbHMpCiMgcGxzX21vZGVsID0gcGxzcih5fngsbmNvbXAgPSA1LHZhbGlkYXRpb249IkxPTyIpCiMgZXZhbCA9IE1TRVAocGxzX21vZGVsKQojIAojIHlfcGNhID0gcHJjb21wKHkpCiMgcGxvdCh5X3BjYSkKIyBleHBsYWluZWRfdmFyID0geV9wY2Ekc2Rldl4yL3N1bSh5X3BjYSRzZGV2XjIpCiMgeV9wY2FfbWF0cml4ID0geV9wY2EkeFssMToxMF0KIyAKIyAjIHJlZ3Jlc3Mgb3V0IHNleCwgd2VpZ2h0CiMgCiMgZ2V0X2V4cGxhaW5lZF92YXJpYW5jZV91c2luZ19QQ0EoeCx5KQojIHggPSBhcHBseSh4LDIscmVncmVzc19vdXQsY292cz1jb3ZzKQojIHkgPSBhcHBseSh5LDIscmVncmVzc19vdXQsY292cz1jb3ZzKQojIGdldF9leHBsYWluZWRfdmFyaWFuY2VfdXNpbmdfUENBKHgseSkKCgpgYGAKCgoKCgoKCgoKCgoKCg==